Apply go fix updates
- commit
- da71e7872ac3d5a0d85cdc16d0946f0bec946942
- author
- brice · · 2026-07-04 06:56 UTC
- parent
- 3170de9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
92 files · +228 -369
M cmd/dumber/main.go +2 -2
| @@ -825,8 +825,8 @@ func isCEFSubprocess(args []string) bool { | |||
| 825 | 825 | return false | |
| 826 | 826 | } | |
| 827 | 827 | ||
| 828 | - | if strings.HasPrefix(args[1], "--type=") { | |
| 829 | - | value := strings.TrimPrefix(args[1], "--type=") | |
| 828 | + | if after, ok := strings.CutPrefix(args[1], "--type="); ok { | |
| 829 | + | value := after | |
| 830 | 830 | return value != "" && !strings.HasPrefix(value, "-") | |
| 831 | 831 | } | |
| 832 | 832 | ||
| @@ -825,8 +825,8 @@ func isCEFSubprocess(args []string) bool { | |||||
| 825 | return false | 825 | return false | ||
| 826 | } | 826 | } | ||
| 827 | 827 | ||||
| 828 | - | if strings.HasPrefix(args[1], "--type=") { | 828 | + | if after, ok := strings.CutPrefix(args[1], "--type="); ok { |
| 829 | - | value := strings.TrimPrefix(args[1], "--type=") | 829 | + | value := after |
| 830 | return value != "" && !strings.HasPrefix(value, "-") | 830 | return value != "" && !strings.HasPrefix(value, "-") | ||
| 831 | } | 831 | } | ||
| 832 | 832 | ||||
M internal/application/usecase/check_runtime_dependencies.go +2 -5
| @@ -159,12 +159,9 @@ func compareVersion(a, b string) (cmp int, ok bool) { | |||
| 159 | 159 | return 0, false | |
| 160 | 160 | } | |
| 161 | 161 | ||
| 162 | - | maxLen := len(av) | |
| 163 | - | if len(bv) > maxLen { | |
| 164 | - | maxLen = len(bv) | |
| 165 | - | } | |
| 162 | + | maxLen := max(len(bv), len(av)) | |
| 166 | 163 | ||
| 167 | - | for i := 0; i < maxLen; i++ { | |
| 164 | + | for i := range maxLen { | |
| 168 | 165 | x := 0 | |
| 169 | 166 | if i < len(av) { | |
| 170 | 167 | x = av[i] | |
| @@ -159,12 +159,9 @@ func compareVersion(a, b string) (cmp int, ok bool) { | |||||
| 159 | return 0, false | 159 | return 0, false | ||
| 160 | } | 160 | } | ||
| 161 | 161 | ||||
| 162 | - | maxLen := len(av) | 162 | + | maxLen := max(len(bv), len(av)) |
| 163 | - | if len(bv) > maxLen { | |||
| 164 | - | maxLen = len(bv) | |||
| 165 | - | } | |||
| 166 | 163 | ||||
| 167 | - | for i := 0; i < maxLen; i++ { | 164 | + | for i := range maxLen { |
| 168 | x := 0 | 165 | x := 0 | ||
| 169 | if i < len(av) { | 166 | if i < len(av) { | ||
| 170 | x = av[i] | 167 | x = av[i] | ||
M internal/application/usecase/favicon.go +2 -7
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "errors" | |
| 6 | 6 | "fmt" | |
| 7 | + | "slices" | |
| 7 | 8 | "sync" | |
| 8 | 9 | "time" | |
| 9 | 10 | ||
| @@ -395,13 +396,7 @@ func (uc *FaviconUseCase) observeFetched(ctx context.Context, requestedPageURL s | |||
| 395 | 396 | return ErrFaviconMiss | |
| 396 | 397 | } | |
| 397 | 398 | if fetched.ResolvedKey != "" { | |
| 398 | - | allowed := false | |
| 399 | - | for _, candidate := range favicon.Candidates(requestedPageURL) { | |
| 400 | - | if candidate == fetched.ResolvedKey { | |
| 401 | - | allowed = true | |
| 402 | - | break | |
| 403 | - | } | |
| 404 | - | } | |
| 399 | + | allowed := slices.Contains(favicon.Candidates(requestedPageURL), fetched.ResolvedKey) | |
| 405 | 400 | if !allowed { | |
| 406 | 401 | return ErrFaviconMiss | |
| 407 | 402 | } | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "errors" | 5 | "errors" | ||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | + | "slices" | |||
| 7 | "sync" | 8 | "sync" | ||
| 8 | "time" | 9 | "time" | ||
| 9 | 10 | ||||
| @@ -395,13 +396,7 @@ func (uc *FaviconUseCase) observeFetched(ctx context.Context, requestedPageURL s | |||||
| 395 | return ErrFaviconMiss | 396 | return ErrFaviconMiss | ||
| 396 | } | 397 | } | ||
| 397 | if fetched.ResolvedKey != "" { | 398 | if fetched.ResolvedKey != "" { | ||
| 398 | - | allowed := false | 399 | + | allowed := slices.Contains(favicon.Candidates(requestedPageURL), fetched.ResolvedKey) |
| 399 | - | for _, candidate := range favicon.Candidates(requestedPageURL) { | |||
| 400 | - | if candidate == fetched.ResolvedKey { | |||
| 401 | - | allowed = true | |||
| 402 | - | break | |||
| 403 | - | } | |||
| 404 | - | } | |||
| 405 | if !allowed { | 400 | if !allowed { | ||
| 406 | return ErrFaviconMiss | 401 | return ErrFaviconMiss | ||
| 407 | } | 402 | } | ||
M internal/application/usecase/favicon_test.go +1 -1
| @@ -128,7 +128,7 @@ func TestFaviconResolveRepairOrphanWithoutConverterReturnsMiss(t *testing.T) { | |||
| 128 | 128 | func TestFaviconResolveStaleSchedulesDedupedBackgroundRefresh(t *testing.T) { | |
| 129 | 129 | fx := newFaviconFixture(t) | |
| 130 | 130 | fx.seed("example.com", []byte("old"), true) | |
| 131 | - | for i := 0; i < 2; i++ { | |
| 131 | + | for i := range 2 { | |
| 132 | 132 | got, err := fx.uc.Resolve(context.Background(), "https://example.com", 32, ResolveOptions{ScheduleBackgroundRefresh: true}) | |
| 133 | 133 | if err != nil { | |
| 134 | 134 | t.Fatal(err) | |
| @@ -128,7 +128,7 @@ func TestFaviconResolveRepairOrphanWithoutConverterReturnsMiss(t *testing.T) { | |||||
| 128 | func TestFaviconResolveStaleSchedulesDedupedBackgroundRefresh(t *testing.T) { | 128 | func TestFaviconResolveStaleSchedulesDedupedBackgroundRefresh(t *testing.T) { | ||
| 129 | fx := newFaviconFixture(t) | 129 | fx := newFaviconFixture(t) | ||
| 130 | fx.seed("example.com", []byte("old"), true) | 130 | fx.seed("example.com", []byte("old"), true) | ||
| 131 | - | for i := 0; i < 2; i++ { | 131 | + | for i := range 2 { |
| 132 | got, err := fx.uc.Resolve(context.Background(), "https://example.com", 32, ResolveOptions{ScheduleBackgroundRefresh: true}) | 132 | got, err := fx.uc.Resolve(context.Background(), "https://example.com", 32, ResolveOptions{ScheduleBackgroundRefresh: true}) | ||
| 133 | if err != nil { | 133 | if err != nil { | ||
| 134 | t.Fatal(err) | 134 | t.Fatal(err) | ||
M internal/application/usecase/handle_permission.go +2 -6
| @@ -4,6 +4,7 @@ package usecase | |||
| 4 | 4 | import ( | |
| 5 | 5 | "context" | |
| 6 | 6 | "errors" | |
| 7 | + | "slices" | |
| 7 | 8 | "sync" | |
| 8 | 9 | "time" | |
| 9 | 10 | ||
| @@ -59,12 +60,7 @@ func (uc *HandlePermissionUseCase) logger(ctx context.Context) *zerolog.Logger { | |||
| 59 | 60 | } | |
| 60 | 61 | ||
| 61 | 62 | func isWebsiteDataAccessPermission(permTypes []entity.PermissionType) bool { | |
| 62 | - | for _, permType := range permTypes { | |
| 63 | - | if permType == entity.PermissionTypeWebsiteDataAccess { | |
| 64 | - | return true | |
| 65 | - | } | |
| 66 | - | } | |
| 67 | - | return false | |
| 63 | + | return slices.Contains(permTypes, entity.PermissionTypeWebsiteDataAccess) | |
| 68 | 64 | } | |
| 69 | 65 | ||
| 70 | 66 | func permissionLoggerWithMetadata(log *zerolog.Logger, metadata entity.PermissionMetadata) *zerolog.Logger { | |
| @@ -4,6 +4,7 @@ package usecase | |||||
| 4 | import ( | 4 | import ( | ||
| 5 | "context" | 5 | "context" | ||
| 6 | "errors" | 6 | "errors" | ||
| 7 | + | "slices" | |||
| 7 | "sync" | 8 | "sync" | ||
| 8 | "time" | 9 | "time" | ||
| 9 | 10 | ||||
| @@ -59,12 +60,7 @@ func (uc *HandlePermissionUseCase) logger(ctx context.Context) *zerolog.Logger { | |||||
| 59 | } | 60 | } | ||
| 60 | 61 | ||||
| 61 | func isWebsiteDataAccessPermission(permTypes []entity.PermissionType) bool { | 62 | func isWebsiteDataAccessPermission(permTypes []entity.PermissionType) bool { | ||
| 62 | - | for _, permType := range permTypes { | 63 | + | return slices.Contains(permTypes, entity.PermissionTypeWebsiteDataAccess) |
| 63 | - | if permType == entity.PermissionTypeWebsiteDataAccess { | |||
| 64 | - | return true | |||
| 65 | - | } | |||
| 66 | - | } | |||
| 67 | - | return false | |||
| 68 | } | 64 | } | ||
| 69 | 65 | ||||
| 70 | func permissionLoggerWithMetadata(log *zerolog.Logger, metadata entity.PermissionMetadata) *zerolog.Logger { | 66 | func permissionLoggerWithMetadata(log *zerolog.Logger, metadata entity.PermissionMetadata) *zerolog.Logger { | ||
M internal/application/usecase/history_recorder.go +3 -3
| @@ -634,7 +634,7 @@ func (uc *HistoryRecorderUseCase) persistHistory(ctx context.Context, record his | |||
| 634 | 634 | return delta | |
| 635 | 635 | } | |
| 636 | 636 | written := 0 | |
| 637 | - | for i := 0; i < delta; i++ { | |
| 637 | + | for range delta { | |
| 638 | 638 | if err := uc.historyRepo.IncrementVisitCount(ctx, record.url); err != nil { | |
| 639 | 639 | log.Warn().Err(err).Str("url", logging.RedactURL(record.url)).Msg("failed to increment visit count") | |
| 640 | 640 | return written | |
| @@ -748,8 +748,8 @@ func normalizePathForHistory(path string) string { | |||
| 748 | 748 | if path == "/" { | |
| 749 | 749 | return "" | |
| 750 | 750 | } | |
| 751 | - | if strings.HasSuffix(path, "/") { | |
| 752 | - | return strings.TrimSuffix(path, "/") | |
| 751 | + | if before, ok := strings.CutSuffix(path, "/"); ok { | |
| 752 | + | return before | |
| 753 | 753 | } | |
| 754 | 754 | return path | |
| 755 | 755 | } | |
| @@ -634,7 +634,7 @@ func (uc *HistoryRecorderUseCase) persistHistory(ctx context.Context, record his | |||||
| 634 | return delta | 634 | return delta | ||
| 635 | } | 635 | } | ||
| 636 | written := 0 | 636 | written := 0 | ||
| 637 | - | for i := 0; i < delta; i++ { | 637 | + | for range delta { |
| 638 | if err := uc.historyRepo.IncrementVisitCount(ctx, record.url); err != nil { | 638 | if err := uc.historyRepo.IncrementVisitCount(ctx, record.url); err != nil { | ||
| 639 | log.Warn().Err(err).Str("url", logging.RedactURL(record.url)).Msg("failed to increment visit count") | 639 | log.Warn().Err(err).Str("url", logging.RedactURL(record.url)).Msg("failed to increment visit count") | ||
| 640 | return written | 640 | return written | ||
| @@ -748,8 +748,8 @@ func normalizePathForHistory(path string) string { | |||||
| 748 | if path == "/" { | 748 | if path == "/" { | ||
| 749 | return "" | 749 | return "" | ||
| 750 | } | 750 | } | ||
| 751 | - | if strings.HasSuffix(path, "/") { | 751 | + | if before, ok := strings.CutSuffix(path, "/"); ok { |
| 752 | - | return strings.TrimSuffix(path, "/") | 752 | + | return before |
| 753 | } | 753 | } | ||
| 754 | return path | 754 | return path | ||
| 755 | } | 755 | } | ||
M internal/application/usecase/manage_panes_consume_expel_test.go +5 -4
| @@ -2,6 +2,7 @@ package usecase | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "strings" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| @@ -424,15 +425,15 @@ func panesInOrder(stackNode *entity.PaneNode) string { | |||
| 424 | 425 | if stackNode == nil || len(stackNode.Children) == 0 { | |
| 425 | 426 | return "" | |
| 426 | 427 | } | |
| 427 | - | out := "" | |
| 428 | + | var out strings.Builder | |
| 428 | 429 | for i, child := range stackNode.Children { | |
| 429 | 430 | if child == nil || child.Pane == nil { | |
| 430 | 431 | continue | |
| 431 | 432 | } | |
| 432 | 433 | if i > 0 { | |
| 433 | - | out += "," | |
| 434 | + | out.WriteString(",") | |
| 434 | 435 | } | |
| 435 | - | out += string(child.Pane.ID) | |
| 436 | + | out.WriteString(string(child.Pane.ID)) | |
| 436 | 437 | } | |
| 437 | - | return out | |
| 438 | + | return out.String() | |
| 438 | 439 | } | |
| @@ -2,6 +2,7 @@ package usecase | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | + | "strings" | |||
| 5 | "testing" | 6 | "testing" | ||
| 6 | 7 | ||||
| 7 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| @@ -424,15 +425,15 @@ func panesInOrder(stackNode *entity.PaneNode) string { | |||||
| 424 | if stackNode == nil || len(stackNode.Children) == 0 { | 425 | if stackNode == nil || len(stackNode.Children) == 0 { | ||
| 425 | return "" | 426 | return "" | ||
| 426 | } | 427 | } | ||
| 427 | - | out := "" | 428 | + | var out strings.Builder |
| 428 | for i, child := range stackNode.Children { | 429 | for i, child := range stackNode.Children { | ||
| 429 | if child == nil || child.Pane == nil { | 430 | if child == nil || child.Pane == nil { | ||
| 430 | continue | 431 | continue | ||
| 431 | } | 432 | } | ||
| 432 | if i > 0 { | 433 | if i > 0 { | ||
| 433 | - | out += "," | 434 | + | out.WriteString(",") |
| 434 | } | 435 | } | ||
| 435 | - | out += string(child.Pane.ID) | 436 | + | out.WriteString(string(child.Pane.ID)) |
| 436 | } | 437 | } | ||
| 437 | - | return out | 438 | + | return out.String() |
| 438 | } | 439 | } | ||
M internal/bootstrap/crash_report_test.go +1 -1
| @@ -61,7 +61,7 @@ func TestReadRedactedLogTail_RingBuffer(t *testing.T) { | |||
| 61 | 61 | path := filepath.Join(dir, "test.log") | |
| 62 | 62 | ||
| 63 | 63 | var lines []string | |
| 64 | - | for i := 0; i < 10; i++ { | |
| 64 | + | for i := range 10 { | |
| 65 | 65 | lines = append(lines, fmt.Sprintf("line %d", i)) | |
| 66 | 66 | } | |
| 67 | 67 | require.NoError(t, os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), filePerm)) | |
| @@ -61,7 +61,7 @@ func TestReadRedactedLogTail_RingBuffer(t *testing.T) { | |||||
| 61 | path := filepath.Join(dir, "test.log") | 61 | path := filepath.Join(dir, "test.log") | ||
| 62 | 62 | ||||
| 63 | var lines []string | 63 | var lines []string | ||
| 64 | - | for i := 0; i < 10; i++ { | 64 | + | for i := range 10 { |
| 65 | lines = append(lines, fmt.Sprintf("line %d", i)) | 65 | lines = append(lines, fmt.Sprintf("line %d", i)) | ||
| 66 | } | 66 | } | ||
| 67 | require.NoError(t, os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), filePerm)) | 67 | require.NoError(t, os.WriteFile(path, []byte(strings.Join(lines, "\n")+"\n"), filePerm)) | ||
M internal/bootstrap/layershell_preload.go +2 -3
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package bootstrap | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "maps" | |
| 4 | 5 | "os" | |
| 5 | 6 | "path/filepath" | |
| 6 | 7 | "strings" | |
| @@ -83,9 +84,7 @@ func layerShellLibrarySearchPaths(env map[string]string) []string { | |||
| 83 | 84 | ||
| 84 | 85 | func LayerShellPreloadEnv(env map[string]string, libraryPath string) map[string]string { | |
| 85 | 86 | cloned := make(map[string]string, len(env)+1) | |
| 86 | - | for key, value := range env { | |
| 87 | - | cloned[key] = value | |
| 88 | - | } | |
| 87 | + | maps.Copy(cloned, env) | |
| 89 | 88 | ||
| 90 | 89 | if libraryPath == "" { | |
| 91 | 90 | return cloned | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package bootstrap | 1 | package bootstrap | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "maps" | |||
| 4 | "os" | 5 | "os" | ||
| 5 | "path/filepath" | 6 | "path/filepath" | ||
| 6 | "strings" | 7 | "strings" | ||
| @@ -83,9 +84,7 @@ func layerShellLibrarySearchPaths(env map[string]string) []string { | |||||
| 83 | 84 | ||||
| 84 | func LayerShellPreloadEnv(env map[string]string, libraryPath string) map[string]string { | 85 | func LayerShellPreloadEnv(env map[string]string, libraryPath string) map[string]string { | ||
| 85 | cloned := make(map[string]string, len(env)+1) | 86 | cloned := make(map[string]string, len(env)+1) | ||
| 86 | - | for key, value := range env { | 87 | + | maps.Copy(cloned, env) |
| 87 | - | cloned[key] = value | |||
| 88 | - | } | |||
| 89 | 88 | ||||
| 90 | if libraryPath == "" { | 89 | if libraryPath == "" { | ||
| 91 | return cloned | 90 | return cloned | ||
M internal/bootstrap/layershell_preload_test.go +3 -6
| @@ -1,5 +1,7 @@ | |||
| 1 | 1 | package bootstrap | |
| 2 | 2 | ||
| 3 | + | import "slices" | |
| 4 | + | ||
| 3 | 5 | import "testing" | |
| 4 | 6 | ||
| 5 | 7 | func TestShouldPreloadLayerShell_WaylandWithoutPreloadReturnsTrue(t *testing.T) { | |
| @@ -116,10 +118,5 @@ func TestLayerShellPreloadEnv_PrependsLibraryToExistingLDPreload(t *testing.T) { | |||
| 116 | 118 | } | |
| 117 | 119 | ||
| 118 | 120 | func containsString(items []string, want string) bool { | |
| 119 | - | for _, item := range items { | |
| 120 | - | if item == want { | |
| 121 | - | return true | |
| 122 | - | } | |
| 123 | - | } | |
| 124 | - | return false | |
| 121 | + | return slices.Contains(items, want) | |
| 125 | 122 | } | |
| @@ -1,5 +1,7 @@ | |||||
| 1 | package bootstrap | 1 | package bootstrap | ||
| 2 | 2 | ||||
| 3 | + | import "slices" | |||
| 4 | + | ||||
| 3 | import "testing" | 5 | import "testing" | ||
| 4 | 6 | ||||
| 5 | func TestShouldPreloadLayerShell_WaylandWithoutPreloadReturnsTrue(t *testing.T) { | 7 | func TestShouldPreloadLayerShell_WaylandWithoutPreloadReturnsTrue(t *testing.T) { | ||
| @@ -116,10 +118,5 @@ func TestLayerShellPreloadEnv_PrependsLibraryToExistingLDPreload(t *testing.T) { | |||||
| 116 | } | 118 | } | ||
| 117 | 119 | ||||
| 118 | func containsString(items []string, want string) bool { | 120 | func containsString(items []string, want string) bool { | ||
| 119 | - | for _, item := range items { | 121 | + | return slices.Contains(items, want) |
| 120 | - | if item == want { | |||
| 121 | - | return true | |||
| 122 | - | } | |||
| 123 | - | } | |||
| 124 | - | return false | |||
| 125 | } | 122 | } | ||
M internal/bootstrap/runtime_config.go +3 -3
| @@ -1,6 +1,8 @@ | |||
| 1 | 1 | package bootstrap | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "maps" | |
| 5 | + | ||
| 4 | 6 | "github.com/bnema/dumber/internal/application/port" | |
| 5 | 7 | "github.com/bnema/dumber/internal/domain/entity" | |
| 6 | 8 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| @@ -131,9 +133,7 @@ func cloneRuntimeSearchShortcuts(in map[string]entity.RuntimeSearchShortcut) map | |||
| 131 | 133 | return nil | |
| 132 | 134 | } | |
| 133 | 135 | out := make(map[string]entity.RuntimeSearchShortcut, len(in)) | |
| 134 | - | for key, shortcut := range in { | |
| 135 | - | out[key] = shortcut | |
| 136 | - | } | |
| 136 | + | maps.Copy(out, in) | |
| 137 | 137 | return out | |
| 138 | 138 | } | |
| 139 | 139 | ||
| @@ -1,6 +1,8 @@ | |||||
| 1 | package bootstrap | 1 | package bootstrap | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "maps" | |||
| 5 | + | ||||
| 4 | "github.com/bnema/dumber/internal/application/port" | 6 | "github.com/bnema/dumber/internal/application/port" | ||
| 5 | "github.com/bnema/dumber/internal/domain/entity" | 7 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 6 | "github.com/bnema/dumber/internal/infrastructure/config" | 8 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| @@ -131,9 +133,7 @@ func cloneRuntimeSearchShortcuts(in map[string]entity.RuntimeSearchShortcut) map | |||||
| 131 | return nil | 133 | return nil | ||
| 132 | } | 134 | } | ||
| 133 | out := make(map[string]entity.RuntimeSearchShortcut, len(in)) | 135 | out := make(map[string]entity.RuntimeSearchShortcut, len(in)) | ||
| 134 | - | for key, shortcut := range in { | 136 | + | maps.Copy(out, in) |
| 135 | - | out[key] = shortcut | |||
| 136 | - | } | |||
| 137 | return out | 137 | return out | ||
| 138 | } | 138 | } | ||
| 139 | 139 | ||||
M internal/cli/cmd/sessions_test.go +1 -1
| @@ -67,7 +67,7 @@ func TestFindSessionByIDOrSuffixSearchesBeyondDefaultLimit(t *testing.T) { | |||
| 67 | 67 | targetID := entity.SessionID("20251224_000000_targetolder") | |
| 68 | 68 | sessions := make([]*entity.Session, 0, defaultSessionsLimit*5+1) | |
| 69 | 69 | startedAt := time.Date(2025, 12, 24, 12, 0, 0, 0, time.UTC) | |
| 70 | - | for i := 0; i < defaultSessionsLimit*5; i++ { | |
| 70 | + | for i := range defaultSessionsLimit * 5 { | |
| 71 | 71 | sessions = append(sessions, &entity.Session{ | |
| 72 | 72 | ID: entity.SessionID(fmt.Sprintf("20251224_1200%02d_recent", i)), | |
| 73 | 73 | Type: entity.SessionTypeBrowser, | |
| @@ -67,7 +67,7 @@ func TestFindSessionByIDOrSuffixSearchesBeyondDefaultLimit(t *testing.T) { | |||||
| 67 | targetID := entity.SessionID("20251224_000000_targetolder") | 67 | targetID := entity.SessionID("20251224_000000_targetolder") | ||
| 68 | sessions := make([]*entity.Session, 0, defaultSessionsLimit*5+1) | 68 | sessions := make([]*entity.Session, 0, defaultSessionsLimit*5+1) | ||
| 69 | startedAt := time.Date(2025, 12, 24, 12, 0, 0, 0, time.UTC) | 69 | startedAt := time.Date(2025, 12, 24, 12, 0, 0, 0, time.UTC) | ||
| 70 | - | for i := 0; i < defaultSessionsLimit*5; i++ { | 70 | + | for i := range defaultSessionsLimit * 5 { |
| 71 | sessions = append(sessions, &entity.Session{ | 71 | sessions = append(sessions, &entity.Session{ | ||
| 72 | ID: entity.SessionID(fmt.Sprintf("20251224_1200%02d_recent", i)), | 72 | ID: entity.SessionID(fmt.Sprintf("20251224_1200%02d_recent", i)), | ||
| 73 | Type: entity.SessionTypeBrowser, | 73 | Type: entity.SessionTypeBrowser, | ||
M internal/cli/model/dmenu.go +3 -4
| @@ -172,10 +172,9 @@ func (m *DmenuModel) updateList() { | |||
| 172 | 172 | } | |
| 173 | 173 | ||
| 174 | 174 | // Create list | |
| 175 | - | listHeight := m.height - 6 // Account for search, help | |
| 176 | - | if listHeight < 5 { | |
| 177 | - | listHeight = 5 | |
| 178 | - | } | |
| 175 | + | listHeight := max( | |
| 176 | + | // Account for search, help | |
| 177 | + | m.height-6, 5) | |
| 179 | 178 | ||
| 180 | 179 | m.list = styles.NewHistoryList(m.theme, items, m.width, listHeight) | |
| 181 | 180 | } | |
| @@ -172,10 +172,9 @@ func (m *DmenuModel) updateList() { | |||||
| 172 | } | 172 | } | ||
| 173 | 173 | ||||
| 174 | // Create list | 174 | // Create list | ||
| 175 | - | listHeight := m.height - 6 // Account for search, help | 175 | + | listHeight := max( |
| 176 | - | if listHeight < 5 { | 176 | + | // Account for search, help |
| 177 | - | listHeight = 5 | 177 | + | m.height-6, 5) |
| 178 | - | } | |||
| 179 | 178 | ||||
| 180 | m.list = styles.NewHistoryList(m.theme, items, m.width, listHeight) | 179 | m.list = styles.NewHistoryList(m.theme, items, m.width, listHeight) | ||
| 181 | } | 180 | } | ||
M internal/cli/model/history.go +3 -4
| @@ -412,10 +412,9 @@ func (m *HistoryModel) updateList() { | |||
| 412 | 412 | } | |
| 413 | 413 | ||
| 414 | 414 | // Create new list | |
| 415 | - | listHeight := m.height - 8 // Account for tabs, search, help | |
| 416 | - | if listHeight < 5 { | |
| 417 | - | listHeight = 5 | |
| 418 | - | } | |
| 415 | + | listHeight := max( | |
| 416 | + | // Account for tabs, search, help | |
| 417 | + | m.height-8, 5) | |
| 419 | 418 | ||
| 420 | 419 | m.list = styles.NewHistoryList(m.theme, items, m.width, listHeight) | |
| 421 | 420 | } | |
| @@ -412,10 +412,9 @@ func (m *HistoryModel) updateList() { | |||||
| 412 | } | 412 | } | ||
| 413 | 413 | ||||
| 414 | // Create new list | 414 | // Create new list | ||
| 415 | - | listHeight := m.height - 8 // Account for tabs, search, help | 415 | + | listHeight := max( |
| 416 | - | if listHeight < 5 { | 416 | + | // Account for tabs, search, help |
| 417 | - | listHeight = 5 | 417 | + | m.height-8, 5) |
| 418 | - | } | |||
| 419 | 418 | ||||
| 420 | m.list = styles.NewHistoryList(m.theme, items, m.width, listHeight) | 419 | m.list = styles.NewHistoryList(m.theme, items, m.width, listHeight) | ||
| 421 | } | 420 | } | ||
M internal/cli/model/sessions.go +4 -8
| @@ -386,10 +386,9 @@ func (m SessionsModel) View() string { | |||
| 386 | 386 | // are many sessions. | |
| 387 | 387 | prefixH := lipgloss.Height(prefix.String()) | |
| 388 | 388 | helpH := lipgloss.Height(helpView) | |
| 389 | - | listH := m.height - prefixH - 1 - helpH // one spacer line between list and help | |
| 390 | - | if listH < 1 { | |
| 391 | - | listH = 1 | |
| 392 | - | } | |
| 389 | + | listH := max( | |
| 390 | + | // one spacer line between list and help | |
| 391 | + | m.height-prefixH-1-helpH, 1) | |
| 393 | 392 | ||
| 394 | 393 | listView := m.renderSessionsList(listH) | |
| 395 | 394 | ||
| @@ -436,10 +435,7 @@ func (m SessionsModel) renderSessionsList(maxHeight int) string { | |||
| 436 | 435 | ||
| 437 | 436 | // Determine starting index so the selected row stays visible even when | |
| 438 | 437 | // rows above it are expanded and consume multiple lines. | |
| 439 | - | start := m.selectedIdx | |
| 440 | - | if start < 0 { | |
| 441 | - | start = 0 | |
| 442 | - | } | |
| 438 | + | start := max(m.selectedIdx, 0) | |
| 443 | 439 | if start >= len(m.sessions) { | |
| 444 | 440 | start = len(m.sessions) - 1 | |
| 445 | 441 | } | |
| @@ -386,10 +386,9 @@ func (m SessionsModel) View() string { | |||||
| 386 | // are many sessions. | 386 | // are many sessions. | ||
| 387 | prefixH := lipgloss.Height(prefix.String()) | 387 | prefixH := lipgloss.Height(prefix.String()) | ||
| 388 | helpH := lipgloss.Height(helpView) | 388 | helpH := lipgloss.Height(helpView) | ||
| 389 | - | listH := m.height - prefixH - 1 - helpH // one spacer line between list and help | 389 | + | listH := max( |
| 390 | - | if listH < 1 { | 390 | + | // one spacer line between list and help |
| 391 | - | listH = 1 | 391 | + | m.height-prefixH-1-helpH, 1) |
| 392 | - | } | |||
| 393 | 392 | ||||
| 394 | listView := m.renderSessionsList(listH) | 393 | listView := m.renderSessionsList(listH) | ||
| 395 | 394 | ||||
| @@ -436,10 +435,7 @@ func (m SessionsModel) renderSessionsList(maxHeight int) string { | |||||
| 436 | 435 | ||||
| 437 | // Determine starting index so the selected row stays visible even when | 436 | // Determine starting index so the selected row stays visible even when | ||
| 438 | // rows above it are expanded and consume multiple lines. | 437 | // rows above it are expanded and consume multiple lines. | ||
| 439 | - | start := m.selectedIdx | 438 | + | start := max(m.selectedIdx, 0) |
| 440 | - | if start < 0 { | |||
| 441 | - | start = 0 | |||
| 442 | - | } | |||
| 443 | if start >= len(m.sessions) { | 439 | if start >= len(m.sessions) { | ||
| 444 | start = len(m.sessions) - 1 | 440 | start = len(m.sessions) - 1 | ||
| 445 | } | 441 | } | ||
M internal/cli/model/sessions_test.go +2 -2
| @@ -18,7 +18,7 @@ func TestRenderSessionsList_KeepsSelectedVisibleWhenExpandedRowAbove(t *testing. | |||
| 18 | 18 | m := NewSessionsModel(context.Background(), theme, SessionsModelConfig{}) | |
| 19 | 19 | ||
| 20 | 20 | m.sessions = make([]entity.SessionInfo, 0, 6) | |
| 21 | - | for i := 0; i < 6; i++ { | |
| 21 | + | for i := range 6 { | |
| 22 | 22 | m.sessions = append(m.sessions, testSessionInfo(fmt.Sprintf("session-%02d", i), 1)) | |
| 23 | 23 | } | |
| 24 | 24 | ||
| @@ -33,7 +33,7 @@ func TestRenderSessionsList_KeepsSelectedVisibleWhenExpandedRowAbove(t *testing. | |||
| 33 | 33 | ||
| 34 | 34 | func testSessionInfo(id string, tabCount int) entity.SessionInfo { | |
| 35 | 35 | tabs := make([]entity.TabSnapshot, 0, tabCount) | |
| 36 | - | for i := 0; i < tabCount; i++ { | |
| 36 | + | for i := range tabCount { | |
| 37 | 37 | tabs = append(tabs, entity.TabSnapshot{Name: fmt.Sprintf("Tab %d", i+1)}) | |
| 38 | 38 | } | |
| 39 | 39 | ||
| @@ -18,7 +18,7 @@ func TestRenderSessionsList_KeepsSelectedVisibleWhenExpandedRowAbove(t *testing. | |||||
| 18 | m := NewSessionsModel(context.Background(), theme, SessionsModelConfig{}) | 18 | m := NewSessionsModel(context.Background(), theme, SessionsModelConfig{}) | ||
| 19 | 19 | ||||
| 20 | m.sessions = make([]entity.SessionInfo, 0, 6) | 20 | m.sessions = make([]entity.SessionInfo, 0, 6) | ||
| 21 | - | for i := 0; i < 6; i++ { | 21 | + | for i := range 6 { |
| 22 | m.sessions = append(m.sessions, testSessionInfo(fmt.Sprintf("session-%02d", i), 1)) | 22 | m.sessions = append(m.sessions, testSessionInfo(fmt.Sprintf("session-%02d", i), 1)) | ||
| 23 | } | 23 | } | ||
| 24 | 24 | ||||
| @@ -33,7 +33,7 @@ func TestRenderSessionsList_KeepsSelectedVisibleWhenExpandedRowAbove(t *testing. | |||||
| 33 | 33 | ||||
| 34 | func testSessionInfo(id string, tabCount int) entity.SessionInfo { | 34 | func testSessionInfo(id string, tabCount int) entity.SessionInfo { | ||
| 35 | tabs := make([]entity.TabSnapshot, 0, tabCount) | 35 | tabs := make([]entity.TabSnapshot, 0, tabCount) | ||
| 36 | - | for i := 0; i < tabCount; i++ { | 36 | + | for i := range tabCount { |
| 37 | tabs = append(tabs, entity.TabSnapshot{Name: fmt.Sprintf("Tab %d", i+1)}) | 37 | tabs = append(tabs, entity.TabSnapshot{Name: fmt.Sprintf("Tab %d", i+1)}) | ||
| 38 | } | 38 | } | ||
| 39 | 39 | ||||
M internal/cli/model/stats.go +6 -14
| @@ -3,6 +3,7 @@ package model | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "strings" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/charmbracelet/bubbles/table" | |
| 8 | 9 | tea "github.com/charmbracelet/bubbletea" | |
| @@ -104,13 +105,7 @@ func (m *StatsModel) updateTable() { | |||
| 104 | 105 | } | |
| 105 | 106 | } | |
| 106 | 107 | ||
| 107 | - | tableHeight := len(rows) | |
| 108 | - | if tableHeight > m.height-10 { | |
| 109 | - | tableHeight = m.height - 10 | |
| 110 | - | } | |
| 111 | - | if tableHeight < 3 { | |
| 112 | - | tableHeight = 3 | |
| 113 | - | } | |
| 108 | + | tableHeight := max(min(len(rows), m.height-10), 3) | |
| 114 | 109 | ||
| 115 | 110 | m.table = styles.NewStyledTable(m.theme, columns, rows, m.width-4, tableHeight) | |
| 116 | 111 | } | |
| @@ -213,7 +208,7 @@ func (m StatsModel) renderDailyActivity() string { | |||
| 213 | 208 | sparklineMaxDays = 30 | |
| 214 | 209 | sparklineMaxIndex = 7 | |
| 215 | 210 | ) | |
| 216 | - | var result string | |
| 211 | + | var result strings.Builder | |
| 217 | 212 | ||
| 218 | 213 | // Show last 30 days or available data | |
| 219 | 214 | days := m.analytics.DailyVisits | |
| @@ -223,14 +218,11 @@ func (m StatsModel) renderDailyActivity() string { | |||
| 223 | 218 | ||
| 224 | 219 | for _, d := range days { | |
| 225 | 220 | // Scale to block index (0-7) | |
| 226 | - | idx := int(float64(d.Visits) / float64(maxVisits) * sparklineMaxIndex) | |
| 227 | - | if idx > sparklineMaxIndex { | |
| 228 | - | idx = sparklineMaxIndex | |
| 229 | - | } | |
| 230 | - | result += string(blocks[idx]) | |
| 221 | + | idx := min(int(float64(d.Visits)/float64(maxVisits)*sparklineMaxIndex), sparklineMaxIndex) | |
| 222 | + | result.WriteString(string(blocks[idx])) | |
| 231 | 223 | } | |
| 232 | 224 | ||
| 233 | - | return lipgloss.NewStyle().Foreground(m.theme.Accent).Render(result) | |
| 225 | + | return lipgloss.NewStyle().Foreground(m.theme.Accent).Render(result.String()) | |
| 234 | 226 | } | |
| 235 | 227 | ||
| 236 | 228 | // formatNumber formats a number for display. | |
| @@ -3,6 +3,7 @@ package model | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "strings" | |||
| 6 | 7 | ||||
| 7 | "github.com/charmbracelet/bubbles/table" | 8 | "github.com/charmbracelet/bubbles/table" | ||
| 8 | tea "github.com/charmbracelet/bubbletea" | 9 | tea "github.com/charmbracelet/bubbletea" | ||
| @@ -104,13 +105,7 @@ func (m *StatsModel) updateTable() { | |||||
| 104 | } | 105 | } | ||
| 105 | } | 106 | } | ||
| 106 | 107 | ||||
| 107 | - | tableHeight := len(rows) | 108 | + | tableHeight := max(min(len(rows), m.height-10), 3) |
| 108 | - | if tableHeight > m.height-10 { | |||
| 109 | - | tableHeight = m.height - 10 | |||
| 110 | - | } | |||
| 111 | - | if tableHeight < 3 { | |||
| 112 | - | tableHeight = 3 | |||
| 113 | - | } | |||
| 114 | 109 | ||||
| 115 | m.table = styles.NewStyledTable(m.theme, columns, rows, m.width-4, tableHeight) | 110 | m.table = styles.NewStyledTable(m.theme, columns, rows, m.width-4, tableHeight) | ||
| 116 | } | 111 | } | ||
| @@ -213,7 +208,7 @@ func (m StatsModel) renderDailyActivity() string { | |||||
| 213 | sparklineMaxDays = 30 | 208 | sparklineMaxDays = 30 | ||
| 214 | sparklineMaxIndex = 7 | 209 | sparklineMaxIndex = 7 | ||
| 215 | ) | 210 | ) | ||
| 216 | - | var result string | 211 | + | var result strings.Builder |
| 217 | 212 | ||||
| 218 | // Show last 30 days or available data | 213 | // Show last 30 days or available data | ||
| 219 | days := m.analytics.DailyVisits | 214 | days := m.analytics.DailyVisits | ||
| @@ -223,14 +218,11 @@ func (m StatsModel) renderDailyActivity() string { | |||||
| 223 | 218 | ||||
| 224 | for _, d := range days { | 219 | for _, d := range days { | ||
| 225 | // Scale to block index (0-7) | 220 | // Scale to block index (0-7) | ||
| 226 | - | idx := int(float64(d.Visits) / float64(maxVisits) * sparklineMaxIndex) | 221 | + | idx := min(int(float64(d.Visits)/float64(maxVisits)*sparklineMaxIndex), sparklineMaxIndex) |
| 227 | - | if idx > sparklineMaxIndex { | 222 | + | result.WriteString(string(blocks[idx])) |
| 228 | - | idx = sparklineMaxIndex | |||
| 229 | - | } | |||
| 230 | - | result += string(blocks[idx]) | |||
| 231 | } | 223 | } | ||
| 232 | 224 | ||||
| 233 | - | return lipgloss.NewStyle().Foreground(m.theme.Accent).Render(result) | 225 | + | return lipgloss.NewStyle().Foreground(m.theme.Accent).Render(result.String()) |
| 234 | } | 226 | } | ||
| 235 | 227 | ||||
| 236 | // formatNumber formats a number for display. | 228 | // formatNumber formats a number for display. | ||
M internal/cli/styles/tabs.go +5 -4
| @@ -2,6 +2,7 @@ package styles | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "fmt" | |
| 5 | + | "strings" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/charmbracelet/lipgloss" | |
| 7 | 8 | ) | |
| @@ -56,16 +57,16 @@ func (m TabsModel) View() string { | |||
| 56 | 57 | Foreground(m.theme.Border). | |
| 57 | 58 | Render(" │ ") | |
| 58 | 59 | ||
| 59 | - | row := "" | |
| 60 | + | var row strings.Builder | |
| 60 | 61 | for i, tab := range tabs { | |
| 61 | 62 | if i > 0 { | |
| 62 | - | row += gap | |
| 63 | + | row.WriteString(gap) | |
| 63 | 64 | } | |
| 64 | - | row += tab | |
| 65 | + | row.WriteString(tab) | |
| 65 | 66 | } | |
| 66 | 67 | ||
| 67 | 68 | const tabBarWidth = 80 | |
| 68 | - | return m.theme.TabBar.Width(tabBarWidth).Render(row) | |
| 69 | + | return m.theme.TabBar.Width(tabBarWidth).Render(row.String()) | |
| 69 | 70 | } | |
| 70 | 71 | ||
| 71 | 72 | // ViewCompact renders a compact tab bar without separators. | |
| @@ -2,6 +2,7 @@ package styles | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | + | "strings" | |||
| 5 | 6 | ||||
| 6 | "github.com/charmbracelet/lipgloss" | 7 | "github.com/charmbracelet/lipgloss" | ||
| 7 | ) | 8 | ) | ||
| @@ -56,16 +57,16 @@ func (m TabsModel) View() string { | |||||
| 56 | Foreground(m.theme.Border). | 57 | Foreground(m.theme.Border). | ||
| 57 | Render(" │ ") | 58 | Render(" │ ") | ||
| 58 | 59 | ||||
| 59 | - | row := "" | 60 | + | var row strings.Builder |
| 60 | for i, tab := range tabs { | 61 | for i, tab := range tabs { | ||
| 61 | if i > 0 { | 62 | if i > 0 { | ||
| 62 | - | row += gap | 63 | + | row.WriteString(gap) |
| 63 | } | 64 | } | ||
| 64 | - | row += tab | 65 | + | row.WriteString(tab) |
| 65 | } | 66 | } | ||
| 66 | 67 | ||||
| 67 | const tabBarWidth = 80 | 68 | const tabBarWidth = 80 | ||
| 68 | - | return m.theme.TabBar.Width(tabBarWidth).Render(row) | 69 | + | return m.theme.TabBar.Width(tabBarWidth).Render(row.String()) |
| 69 | } | 70 | } | ||
| 70 | 71 | ||||
| 71 | // ViewCompact renders a compact tab bar without separators. | 72 | // ViewCompact renders a compact tab bar without separators. | ||
M internal/domain/entity/engine_settings_test.go +3 -3
| @@ -6,13 +6,13 @@ import ( | |||
| 6 | 6 | ) | |
| 7 | 7 | ||
| 8 | 8 | func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | |
| 9 | - | payloadType := reflect.TypeOf(EngineSettingsPayload{}) | |
| 9 | + | payloadType := reflect.TypeFor[EngineSettingsPayload]() | |
| 10 | 10 | for _, field := range []string{"DefaultUIScale", "WebContent"} { | |
| 11 | 11 | if _, ok := payloadType.FieldByName(field); !ok { | |
| 12 | 12 | t.Fatalf("EngineSettingsPayload missing %s", field) | |
| 13 | 13 | } | |
| 14 | 14 | } | |
| 15 | - | webContentType := reflect.TypeOf(EngineWebContentSettingsPayload{}) | |
| 15 | + | webContentType := reflect.TypeFor[EngineWebContentSettingsPayload]() | |
| 16 | 16 | for _, field := range []string{ | |
| 17 | 17 | "SansFont", | |
| 18 | 18 | "SerifFont", | |
| @@ -31,7 +31,7 @@ func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | |||
| 31 | 31 | } | |
| 32 | 32 | ||
| 33 | 33 | func TestEngineSettingsUpdateHasNoRawBridge(t *testing.T) { | |
| 34 | - | updateType := reflect.TypeOf(EngineSettingsUpdate{}) | |
| 34 | + | updateType := reflect.TypeFor[EngineSettingsUpdate]() | |
| 35 | 35 | if _, ok := updateType.FieldByName("Raw"); ok { | |
| 36 | 36 | t.Fatal("EngineSettingsUpdate must not expose Raw legacy bridge") | |
| 37 | 37 | } | |
| @@ -6,13 +6,13 @@ import ( | |||||
| 6 | ) | 6 | ) | ||
| 7 | 7 | ||||
| 8 | func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | 8 | func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | ||
| 9 | - | payloadType := reflect.TypeOf(EngineSettingsPayload{}) | 9 | + | payloadType := reflect.TypeFor[EngineSettingsPayload]() |
| 10 | for _, field := range []string{"DefaultUIScale", "WebContent"} { | 10 | for _, field := range []string{"DefaultUIScale", "WebContent"} { | ||
| 11 | if _, ok := payloadType.FieldByName(field); !ok { | 11 | if _, ok := payloadType.FieldByName(field); !ok { | ||
| 12 | t.Fatalf("EngineSettingsPayload missing %s", field) | 12 | t.Fatalf("EngineSettingsPayload missing %s", field) | ||
| 13 | } | 13 | } | ||
| 14 | } | 14 | } | ||
| 15 | - | webContentType := reflect.TypeOf(EngineWebContentSettingsPayload{}) | 15 | + | webContentType := reflect.TypeFor[EngineWebContentSettingsPayload]() |
| 16 | for _, field := range []string{ | 16 | for _, field := range []string{ | ||
| 17 | "SansFont", | 17 | "SansFont", | ||
| 18 | "SerifFont", | 18 | "SerifFont", | ||
| @@ -31,7 +31,7 @@ func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | |||||
| 31 | } | 31 | } | ||
| 32 | 32 | ||||
| 33 | func TestEngineSettingsUpdateHasNoRawBridge(t *testing.T) { | 33 | func TestEngineSettingsUpdateHasNoRawBridge(t *testing.T) { | ||
| 34 | - | updateType := reflect.TypeOf(EngineSettingsUpdate{}) | 34 | + | updateType := reflect.TypeFor[EngineSettingsUpdate]() |
| 35 | if _, ok := updateType.FieldByName("Raw"); ok { | 35 | if _, ok := updateType.FieldByName("Raw"); ok { | ||
| 36 | t.Fatal("EngineSettingsUpdate must not expose Raw legacy bridge") | 36 | t.Fatal("EngineSettingsUpdate must not expose Raw legacy bridge") | ||
| 37 | } | 37 | } | ||
M internal/infrastructure/audio/null/output_test.go +1 -1
| @@ -64,7 +64,7 @@ func TestStream_Write_AcceptsMultipleCalls(t *testing.T) { | |||
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | 66 | // Act & Assert: multiple writes should succeed | |
| 67 | - | for i := 0; i < 3; i++ { | |
| 67 | + | for i := range 3 { | |
| 68 | 68 | if err := stream.Write(samples); err != nil { | |
| 69 | 69 | t.Errorf("Write #%d failed: %v", i+1, err) | |
| 70 | 70 | } | |
| @@ -64,7 +64,7 @@ func TestStream_Write_AcceptsMultipleCalls(t *testing.T) { | |||||
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | // Act & Assert: multiple writes should succeed | 66 | // Act & Assert: multiple writes should succeed | ||
| 67 | - | for i := 0; i < 3; i++ { | 67 | + | for i := range 3 { |
| 68 | if err := stream.Write(samples); err != nil { | 68 | if err := stream.Write(samples); err != nil { | ||
| 69 | t.Errorf("Write #%d failed: %v", i+1, err) | 69 | t.Errorf("Write #%d failed: %v", i+1, err) | ||
| 70 | } | 70 | } | ||
M internal/infrastructure/audio/pipewire/output.go +2 -14
| @@ -213,13 +213,7 @@ func copyToPCM(pcm *pwpipewire.PCMBuffer, samples [][]float32) int { | |||
| 213 | 213 | } | |
| 214 | 214 | ||
| 215 | 215 | frames := pcm.Frames | |
| 216 | - | channels := len(samples) | |
| 217 | - | if channels > pcm.Channels { | |
| 218 | - | channels = pcm.Channels | |
| 219 | - | } | |
| 220 | - | if channels > len(pcm.Samples) { | |
| 221 | - | channels = len(pcm.Samples) | |
| 222 | - | } | |
| 216 | + | channels := min(min(len(samples), pcm.Channels), len(pcm.Samples)) | |
| 223 | 217 | if channels == 0 { | |
| 224 | 218 | return 0 | |
| 225 | 219 | } | |
| @@ -234,13 +228,7 @@ func copyToPCM(pcm *pwpipewire.PCMBuffer, samples [][]float32) int { | |||
| 234 | 228 | continue | |
| 235 | 229 | } | |
| 236 | 230 | srcFrames := len(src) | |
| 237 | - | n := frames | |
| 238 | - | if srcFrames < n { | |
| 239 | - | n = srcFrames | |
| 240 | - | } | |
| 241 | - | if len(dst) < n { | |
| 242 | - | n = len(dst) | |
| 243 | - | } | |
| 231 | + | n := min(len(dst), min(srcFrames, frames)) | |
| 244 | 232 | if n < minFrames { | |
| 245 | 233 | minFrames = n | |
| 246 | 234 | } | |
| @@ -213,13 +213,7 @@ func copyToPCM(pcm *pwpipewire.PCMBuffer, samples [][]float32) int { | |||||
| 213 | } | 213 | } | ||
| 214 | 214 | ||||
| 215 | frames := pcm.Frames | 215 | frames := pcm.Frames | ||
| 216 | - | channels := len(samples) | 216 | + | channels := min(min(len(samples), pcm.Channels), len(pcm.Samples)) |
| 217 | - | if channels > pcm.Channels { | |||
| 218 | - | channels = pcm.Channels | |||
| 219 | - | } | |||
| 220 | - | if channels > len(pcm.Samples) { | |||
| 221 | - | channels = len(pcm.Samples) | |||
| 222 | - | } | |||
| 223 | if channels == 0 { | 217 | if channels == 0 { | ||
| 224 | return 0 | 218 | return 0 | ||
| 225 | } | 219 | } | ||
| @@ -234,13 +228,7 @@ func copyToPCM(pcm *pwpipewire.PCMBuffer, samples [][]float32) int { | |||||
| 234 | continue | 228 | continue | ||
| 235 | } | 229 | } | ||
| 236 | srcFrames := len(src) | 230 | srcFrames := len(src) | ||
| 237 | - | n := frames | 231 | + | n := min(len(dst), min(srcFrames, frames)) |
| 238 | - | if srcFrames < n { | |||
| 239 | - | n = srcFrames | |||
| 240 | - | } | |||
| 241 | - | if len(dst) < n { | |||
| 242 | - | n = len(dst) | |||
| 243 | - | } | |||
| 244 | if n < minFrames { | 232 | if n < minFrames { | ||
| 245 | minFrames = n | 233 | minFrames = n | ||
| 246 | } | 234 | } | ||
M internal/infrastructure/audio/pipewire/output_test.go +3 -3
| @@ -448,7 +448,7 @@ func TestPlayerStreamAdapter_Write_DropsWhenBufferFull(t *testing.T) { | |||
| 448 | 448 | } | |
| 449 | 449 | ||
| 450 | 450 | // Fill the buffer to capacity | |
| 451 | - | for i := 0; i < 4; i++ { | |
| 451 | + | for i := range 4 { | |
| 452 | 452 | require.NoError(t, adapter.Write([][]float32{{float32(i)}})) | |
| 453 | 453 | } | |
| 454 | 454 | ||
| @@ -474,7 +474,7 @@ func TestPlayerStreamAdapter_Write_DropsLogOnlyOnFirstAndPowerOfTwo(t *testing.T | |||
| 474 | 474 | require.NoError(t, adapter.Write([][]float32{{0.0}})) | |
| 475 | 475 | ||
| 476 | 476 | // Drop 10 packets | |
| 477 | - | for i := 0; i < 10; i++ { | |
| 477 | + | for i := range 10 { | |
| 478 | 478 | _ = adapter.Write([][]float32{{float32(i)}}) | |
| 479 | 479 | } | |
| 480 | 480 | ||
| @@ -495,7 +495,7 @@ func TestPlayerStreamAdapter_ConcurrentWriteClose_NoRace(t *testing.T) { | |||
| 495 | 495 | done := make(chan struct{}) | |
| 496 | 496 | go func() { | |
| 497 | 497 | defer close(done) | |
| 498 | - | for i := 0; i < 100; i++ { | |
| 498 | + | for i := range 100 { | |
| 499 | 499 | err := adapter.Write([][]float32{{float32(i)}}) | |
| 500 | 500 | if err != nil { | |
| 501 | 501 | assert.ErrorIs(t, err, ErrStreamClosed) | |
| @@ -448,7 +448,7 @@ func TestPlayerStreamAdapter_Write_DropsWhenBufferFull(t *testing.T) { | |||||
| 448 | } | 448 | } | ||
| 449 | 449 | ||||
| 450 | // Fill the buffer to capacity | 450 | // Fill the buffer to capacity | ||
| 451 | - | for i := 0; i < 4; i++ { | 451 | + | for i := range 4 { |
| 452 | require.NoError(t, adapter.Write([][]float32{{float32(i)}})) | 452 | require.NoError(t, adapter.Write([][]float32{{float32(i)}})) | ||
| 453 | } | 453 | } | ||
| 454 | 454 | ||||
| @@ -474,7 +474,7 @@ func TestPlayerStreamAdapter_Write_DropsLogOnlyOnFirstAndPowerOfTwo(t *testing.T | |||||
| 474 | require.NoError(t, adapter.Write([][]float32{{0.0}})) | 474 | require.NoError(t, adapter.Write([][]float32{{0.0}})) | ||
| 475 | 475 | ||||
| 476 | // Drop 10 packets | 476 | // Drop 10 packets | ||
| 477 | - | for i := 0; i < 10; i++ { | 477 | + | for i := range 10 { |
| 478 | _ = adapter.Write([][]float32{{float32(i)}}) | 478 | _ = adapter.Write([][]float32{{float32(i)}}) | ||
| 479 | } | 479 | } | ||
| 480 | 480 | ||||
| @@ -495,7 +495,7 @@ func TestPlayerStreamAdapter_ConcurrentWriteClose_NoRace(t *testing.T) { | |||||
| 495 | done := make(chan struct{}) | 495 | done := make(chan struct{}) | ||
| 496 | go func() { | 496 | go func() { | ||
| 497 | defer close(done) | 497 | defer close(done) | ||
| 498 | - | for i := 0; i < 100; i++ { | 498 | + | for i := range 100 { |
| 499 | err := adapter.Write([][]float32{{float32(i)}}) | 499 | err := adapter.Write([][]float32{{float32(i)}}) | ||
| 500 | if err != nil { | 500 | if err != nil { | ||
| 501 | assert.ErrorIs(t, err, ErrStreamClosed) | 501 | assert.ErrorIs(t, err, ErrStreamClosed) | ||
M internal/infrastructure/cache/lru_test.go +3 -3
| @@ -145,7 +145,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) { | |||
| 145 | 145 | var wg sync.WaitGroup | |
| 146 | 146 | ||
| 147 | 147 | // Concurrent writes | |
| 148 | - | for i := 0; i < 100; i++ { | |
| 148 | + | for i := range 100 { | |
| 149 | 149 | wg.Add(1) | |
| 150 | 150 | go func(i int) { | |
| 151 | 151 | defer wg.Done() | |
| @@ -155,7 +155,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) { | |||
| 155 | 155 | wg.Wait() | |
| 156 | 156 | ||
| 157 | 157 | // Concurrent reads | |
| 158 | - | for i := 0; i < 100; i++ { | |
| 158 | + | for i := range 100 { | |
| 159 | 159 | wg.Add(1) | |
| 160 | 160 | go func(i int) { | |
| 161 | 161 | defer wg.Done() | |
| @@ -165,7 +165,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) { | |||
| 165 | 165 | wg.Wait() | |
| 166 | 166 | ||
| 167 | 167 | // Concurrent mixed operations | |
| 168 | - | for i := 0; i < 50; i++ { | |
| 168 | + | for i := range 50 { | |
| 169 | 169 | wg.Add(3) | |
| 170 | 170 | go func(i int) { | |
| 171 | 171 | defer wg.Done() | |
| @@ -145,7 +145,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) { | |||||
| 145 | var wg sync.WaitGroup | 145 | var wg sync.WaitGroup | ||
| 146 | 146 | ||||
| 147 | // Concurrent writes | 147 | // Concurrent writes | ||
| 148 | - | for i := 0; i < 100; i++ { | 148 | + | for i := range 100 { |
| 149 | wg.Add(1) | 149 | wg.Add(1) | ||
| 150 | go func(i int) { | 150 | go func(i int) { | ||
| 151 | defer wg.Done() | 151 | defer wg.Done() | ||
| @@ -155,7 +155,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) { | |||||
| 155 | wg.Wait() | 155 | wg.Wait() | ||
| 156 | 156 | ||||
| 157 | // Concurrent reads | 157 | // Concurrent reads | ||
| 158 | - | for i := 0; i < 100; i++ { | 158 | + | for i := range 100 { |
| 159 | wg.Add(1) | 159 | wg.Add(1) | ||
| 160 | go func(i int) { | 160 | go func(i int) { | ||
| 161 | defer wg.Done() | 161 | defer wg.Done() | ||
| @@ -165,7 +165,7 @@ func TestLRU_ConcurrentAccess(t *testing.T) { | |||||
| 165 | wg.Wait() | 165 | wg.Wait() | ||
| 166 | 166 | ||||
| 167 | // Concurrent mixed operations | 167 | // Concurrent mixed operations | ||
| 168 | - | for i := 0; i < 50; i++ { | 168 | + | for i := range 50 { |
| 169 | wg.Add(3) | 169 | wg.Add(3) | ||
| 170 | go func(i int) { | 170 | go func(i int) { | ||
| 171 | defer wg.Done() | 171 | defer wg.Done() | ||
M internal/infrastructure/cef/app.go +1 -1
| @@ -193,7 +193,7 @@ func appendUniqueCommaSeparatedSwitchValues(commandLine purecef.CommandLine, nam | |||
| 193 | 193 | ||
| 194 | 194 | seen := make(map[string]struct{}, len(values)) | |
| 195 | 195 | combined := make([]string, 0, len(values)) | |
| 196 | - | for _, value := range strings.Split(commandLine.GetSwitchValue(name), ",") { | |
| 196 | + | for value := range strings.SplitSeq(commandLine.GetSwitchValue(name), ",") { | |
| 197 | 197 | value = strings.TrimSpace(value) | |
| 198 | 198 | if value == "" { | |
| 199 | 199 | continue | |
| @@ -193,7 +193,7 @@ func appendUniqueCommaSeparatedSwitchValues(commandLine purecef.CommandLine, nam | |||||
| 193 | 193 | ||||
| 194 | seen := make(map[string]struct{}, len(values)) | 194 | seen := make(map[string]struct{}, len(values)) | ||
| 195 | combined := make([]string, 0, len(values)) | 195 | combined := make([]string, 0, len(values)) | ||
| 196 | - | for _, value := range strings.Split(commandLine.GetSwitchValue(name), ",") { | 196 | + | for value := range strings.SplitSeq(commandLine.GetSwitchValue(name), ",") { |
| 197 | value = strings.TrimSpace(value) | 197 | value = strings.TrimSpace(value) | ||
| 198 | if value == "" { | 198 | if value == "" { | ||
| 199 | continue | 199 | continue | ||
M internal/infrastructure/cef/engine_init.go +4 -5
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "math" | |
| 8 | 8 | "os" | |
| 9 | 9 | "path/filepath" | |
| 10 | + | "slices" | |
| 10 | 11 | "strconv" | |
| 11 | 12 | "strings" | |
| 12 | 13 | "syscall" | |
| @@ -214,7 +215,7 @@ func loadedLibCEFPath() string { | |||
| 214 | 215 | } | |
| 215 | 216 | ||
| 216 | 217 | func parseLoadedLibCEFPath(maps string) string { | |
| 217 | - | for _, line := range strings.Split(maps, "\n") { | |
| 218 | + | for line := range strings.SplitSeq(maps, "\n") { | |
| 218 | 219 | if !strings.Contains(line, "libcef.so") { | |
| 219 | 220 | continue | |
| 220 | 221 | } | |
| @@ -339,10 +340,8 @@ func registerEngineSchemeFactory( | |||
| 339 | 340 | ||
| 340 | 341 | // appendIfMissing appends flag to args if it's not already present. | |
| 341 | 342 | func appendIfMissing(args []string, flag string) []string { | |
| 342 | - | for _, a := range args { | |
| 343 | - | if a == flag { | |
| 344 | - | return args | |
| 345 | - | } | |
| 343 | + | if slices.Contains(args, flag) { | |
| 344 | + | return args | |
| 346 | 345 | } | |
| 347 | 346 | return append(args, flag) | |
| 348 | 347 | } | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "math" | 7 | "math" | ||
| 8 | "os" | 8 | "os" | ||
| 9 | "path/filepath" | 9 | "path/filepath" | ||
| 10 | + | "slices" | |||
| 10 | "strconv" | 11 | "strconv" | ||
| 11 | "strings" | 12 | "strings" | ||
| 12 | "syscall" | 13 | "syscall" | ||
| @@ -214,7 +215,7 @@ func loadedLibCEFPath() string { | |||||
| 214 | } | 215 | } | ||
| 215 | 216 | ||||
| 216 | func parseLoadedLibCEFPath(maps string) string { | 217 | func parseLoadedLibCEFPath(maps string) string { | ||
| 217 | - | for _, line := range strings.Split(maps, "\n") { | 218 | + | for line := range strings.SplitSeq(maps, "\n") { |
| 218 | if !strings.Contains(line, "libcef.so") { | 219 | if !strings.Contains(line, "libcef.so") { | ||
| 219 | continue | 220 | continue | ||
| 220 | } | 221 | } | ||
| @@ -339,10 +340,8 @@ func registerEngineSchemeFactory( | |||||
| 339 | 340 | ||||
| 340 | // appendIfMissing appends flag to args if it's not already present. | 341 | // appendIfMissing appends flag to args if it's not already present. | ||
| 341 | func appendIfMissing(args []string, flag string) []string { | 342 | func appendIfMissing(args []string, flag string) []string { | ||
| 342 | - | for _, a := range args { | 343 | + | if slices.Contains(args, flag) { |
| 343 | - | if a == flag { | 344 | + | return args |
| 344 | - | return args | |||
| 345 | - | } | |||
| 346 | } | 345 | } | ||
| 347 | return append(args, flag) | 346 | return append(args, flag) | ||
| 348 | } | 347 | } | ||
M internal/infrastructure/cef/engine_settings_test.go +1 -1
| @@ -35,7 +35,7 @@ func TestEngineUpdateSettingsUsesZeroScaleWhenPayloadIsEmpty(t *testing.T) { | |||
| 35 | 35 | func TestEngineApplicationScaleConcurrentAccess(_ *testing.T) { | |
| 36 | 36 | e := &Engine{applicationScale: 1} | |
| 37 | 37 | var wg sync.WaitGroup | |
| 38 | - | for i := 0; i < 16; i++ { | |
| 38 | + | for i := range 16 { | |
| 39 | 39 | wg.Add(2) | |
| 40 | 40 | go func() { | |
| 41 | 41 | defer wg.Done() | |
| @@ -35,7 +35,7 @@ func TestEngineUpdateSettingsUsesZeroScaleWhenPayloadIsEmpty(t *testing.T) { | |||||
| 35 | func TestEngineApplicationScaleConcurrentAccess(_ *testing.T) { | 35 | func TestEngineApplicationScaleConcurrentAccess(_ *testing.T) { | ||
| 36 | e := &Engine{applicationScale: 1} | 36 | e := &Engine{applicationScale: 1} | ||
| 37 | var wg sync.WaitGroup | 37 | var wg sync.WaitGroup | ||
| 38 | - | for i := 0; i < 16; i++ { | 38 | + | for i := range 16 { |
| 39 | wg.Add(2) | 39 | wg.Add(2) | ||
| 40 | go func() { | 40 | go func() { | ||
| 41 | defer wg.Done() | 41 | defer wg.Done() | ||
M internal/infrastructure/cef/favicon_discovery.go +2 -2
| @@ -77,8 +77,8 @@ func parseFaviconHrefs(htmlSource string) []string { | |||
| 77 | 77 | } | |
| 78 | 78 | ||
| 79 | 79 | func faviconRel(rel string) bool { | |
| 80 | - | fields := strings.Fields(strings.ToLower(rel)) | |
| 81 | - | for _, field := range fields { | |
| 80 | + | fields := strings.FieldsSeq(strings.ToLower(rel)) | |
| 81 | + | for field := range fields { | |
| 82 | 82 | if field == "icon" || field == "apple-touch-icon" || field == "apple-touch-icon-precomposed" { | |
| 83 | 83 | return true | |
| 84 | 84 | } | |
| @@ -77,8 +77,8 @@ func parseFaviconHrefs(htmlSource string) []string { | |||||
| 77 | } | 77 | } | ||
| 78 | 78 | ||||
| 79 | func faviconRel(rel string) bool { | 79 | func faviconRel(rel string) bool { | ||
| 80 | - | fields := strings.Fields(strings.ToLower(rel)) | 80 | + | fields := strings.FieldsSeq(strings.ToLower(rel)) |
| 81 | - | for _, field := range fields { | 81 | + | for field := range fields { |
| 82 | if field == "icon" || field == "apple-touch-icon" || field == "apple-touch-icon-precomposed" { | 82 | if field == "icon" || field == "apple-touch-icon" || field == "apple-touch-icon-precomposed" { | ||
| 83 | return true | 83 | return true | ||
| 84 | } | 84 | } | ||
M internal/infrastructure/cef/file_dialog.go +1 -1
| @@ -307,7 +307,7 @@ func dialogFilterTokens(spec, extensions string) []string { | |||
| 307 | 307 | if spec = strings.TrimSpace(spec); spec != "" { | |
| 308 | 308 | tokens = append(tokens, spec) | |
| 309 | 309 | } | |
| 310 | - | for _, part := range strings.Split(extensions, ";") { | |
| 310 | + | for part := range strings.SplitSeq(extensions, ";") { | |
| 311 | 311 | part = strings.TrimSpace(part) | |
| 312 | 312 | if part != "" { | |
| 313 | 313 | tokens = append(tokens, part) | |
| @@ -307,7 +307,7 @@ func dialogFilterTokens(spec, extensions string) []string { | |||||
| 307 | if spec = strings.TrimSpace(spec); spec != "" { | 307 | if spec = strings.TrimSpace(spec); spec != "" { | ||
| 308 | tokens = append(tokens, spec) | 308 | tokens = append(tokens, spec) | ||
| 309 | } | 309 | } | ||
| 310 | - | for _, part := range strings.Split(extensions, ";") { | 310 | + | for part := range strings.SplitSeq(extensions, ";") { |
| 311 | part = strings.TrimSpace(part) | 311 | part = strings.TrimSpace(part) | ||
| 312 | if part != "" { | 312 | if part != "" { | ||
| 313 | tokens = append(tokens, part) | 313 | tokens = append(tokens, part) | ||
M internal/infrastructure/cef/file_dialog_test.go +1 -1
| @@ -162,7 +162,7 @@ func TestFolderUploadPathsTruncationCancels(t *testing.T) { | |||
| 162 | 162 | defer func() { maxExpandedFolderUploadFiles = prev }() | |
| 163 | 163 | ||
| 164 | 164 | dir := t.TempDir() | |
| 165 | - | for i := 0; i < 5; i++ { | |
| 165 | + | for i := range 5 { | |
| 166 | 166 | p := filepath.Join(dir, fmt.Sprintf("file_%d.txt", i)) | |
| 167 | 167 | require.NoError(t, os.WriteFile(p, []byte("data"), 0o644)) | |
| 168 | 168 | } | |
| @@ -162,7 +162,7 @@ func TestFolderUploadPathsTruncationCancels(t *testing.T) { | |||||
| 162 | defer func() { maxExpandedFolderUploadFiles = prev }() | 162 | defer func() { maxExpandedFolderUploadFiles = prev }() | ||
| 163 | 163 | ||||
| 164 | dir := t.TempDir() | 164 | dir := t.TempDir() | ||
| 165 | - | for i := 0; i < 5; i++ { | 165 | + | for i := range 5 { |
| 166 | p := filepath.Join(dir, fmt.Sprintf("file_%d.txt", i)) | 166 | p := filepath.Join(dir, fmt.Sprintf("file_%d.txt", i)) | ||
| 167 | require.NoError(t, os.WriteFile(p, []byte("data"), 0o644)) | 167 | require.NoError(t, os.WriteFile(p, []byte("data"), 0o644)) | ||
| 168 | } | 168 | } | ||
M internal/infrastructure/cef/process_diagnostics.go +5 -5
| @@ -168,7 +168,7 @@ func procPPID(pid int) int { | |||
| 168 | 168 | if err != nil { | |
| 169 | 169 | return 0 | |
| 170 | 170 | } | |
| 171 | - | for _, line := range strings.Split(string(b), "\n") { | |
| 171 | + | for line := range strings.SplitSeq(string(b), "\n") { | |
| 172 | 172 | if !strings.HasPrefix(line, "PPid:") { | |
| 173 | 173 | continue | |
| 174 | 174 | } | |
| @@ -190,9 +190,9 @@ func procCmdline(pid int) string { | |||
| 190 | 190 | } | |
| 191 | 191 | ||
| 192 | 192 | func chromiumProcessType(cmdline string) string { | |
| 193 | - | for _, field := range strings.Fields(cmdline) { | |
| 194 | - | if strings.HasPrefix(field, "--type=") { | |
| 195 | - | return strings.TrimPrefix(field, "--type=") | |
| 193 | + | for field := range strings.FieldsSeq(cmdline) { | |
| 194 | + | if after, ok := strings.CutPrefix(field, "--type="); ok { | |
| 195 | + | return after | |
| 196 | 196 | } | |
| 197 | 197 | } | |
| 198 | 198 | return "browser" | |
| @@ -366,7 +366,7 @@ func gpuFDInfoSummaries(pid int) []string { | |||
| 366 | 366 | if err != nil { | |
| 367 | 367 | continue | |
| 368 | 368 | } | |
| 369 | - | for _, line := range strings.Split(string(b), "\n") { | |
| 369 | + | for line := range strings.SplitSeq(string(b), "\n") { | |
| 370 | 370 | if strings.Contains(line, "drm-engine-") || strings.HasPrefix(line, "drm-client-id") || strings.HasPrefix(line, "drm-pdev") { | |
| 371 | 371 | out = append(out, "fd="+entry.Name()+" "+strings.TrimSpace(line)) | |
| 372 | 372 | } | |
| @@ -168,7 +168,7 @@ func procPPID(pid int) int { | |||||
| 168 | if err != nil { | 168 | if err != nil { | ||
| 169 | return 0 | 169 | return 0 | ||
| 170 | } | 170 | } | ||
| 171 | - | for _, line := range strings.Split(string(b), "\n") { | 171 | + | for line := range strings.SplitSeq(string(b), "\n") { |
| 172 | if !strings.HasPrefix(line, "PPid:") { | 172 | if !strings.HasPrefix(line, "PPid:") { | ||
| 173 | continue | 173 | continue | ||
| 174 | } | 174 | } | ||
| @@ -190,9 +190,9 @@ func procCmdline(pid int) string { | |||||
| 190 | } | 190 | } | ||
| 191 | 191 | ||||
| 192 | func chromiumProcessType(cmdline string) string { | 192 | func chromiumProcessType(cmdline string) string { | ||
| 193 | - | for _, field := range strings.Fields(cmdline) { | 193 | + | for field := range strings.FieldsSeq(cmdline) { |
| 194 | - | if strings.HasPrefix(field, "--type=") { | 194 | + | if after, ok := strings.CutPrefix(field, "--type="); ok { |
| 195 | - | return strings.TrimPrefix(field, "--type=") | 195 | + | return after |
| 196 | } | 196 | } | ||
| 197 | } | 197 | } | ||
| 198 | return "browser" | 198 | return "browser" | ||
| @@ -366,7 +366,7 @@ func gpuFDInfoSummaries(pid int) []string { | |||||
| 366 | if err != nil { | 366 | if err != nil { | ||
| 367 | continue | 367 | continue | ||
| 368 | } | 368 | } | ||
| 369 | - | for _, line := range strings.Split(string(b), "\n") { | 369 | + | for line := range strings.SplitSeq(string(b), "\n") { |
| 370 | if strings.Contains(line, "drm-engine-") || strings.HasPrefix(line, "drm-client-id") || strings.HasPrefix(line, "drm-pdev") { | 370 | if strings.Contains(line, "drm-engine-") || strings.HasPrefix(line, "drm-client-id") || strings.HasPrefix(line, "drm-pdev") { | ||
| 371 | out = append(out, "fd="+entry.Name()+" "+strings.TrimSpace(line)) | 371 | out = append(out, "fd="+entry.Name()+" "+strings.TrimSpace(line)) | ||
| 372 | } | 372 | } | ||
M internal/infrastructure/cef/profile.go +1 -4
| @@ -154,10 +154,7 @@ func (wv *WebView) latestCEF2GTKProfileSnapshot(now time.Time) (cef2gtk.ProfileS | |||
| 154 | 154 | if snapshotAt.IsZero() { | |
| 155 | 155 | return cef2gtk.ProfileSnapshot{}, 0, false | |
| 156 | 156 | } | |
| 157 | - | age := now.Sub(snapshotAt) | |
| 158 | - | if age < 0 { | |
| 159 | - | age = 0 | |
| 160 | - | } | |
| 157 | + | age := max(now.Sub(snapshotAt), 0) | |
| 161 | 158 | return snapshot, age, true | |
| 162 | 159 | } | |
| 163 | 160 | ||
| @@ -154,10 +154,7 @@ func (wv *WebView) latestCEF2GTKProfileSnapshot(now time.Time) (cef2gtk.ProfileS | |||||
| 154 | if snapshotAt.IsZero() { | 154 | if snapshotAt.IsZero() { | ||
| 155 | return cef2gtk.ProfileSnapshot{}, 0, false | 155 | return cef2gtk.ProfileSnapshot{}, 0, false | ||
| 156 | } | 156 | } | ||
| 157 | - | age := now.Sub(snapshotAt) | 157 | + | age := max(now.Sub(snapshotAt), 0) |
| 158 | - | if age < 0 { | |||
| 159 | - | age = 0 | |||
| 160 | - | } | |||
| 161 | return snapshot, age, true | 158 | return snapshot, age, true | ||
| 162 | } | 159 | } | ||
| 163 | 160 | ||||
M internal/infrastructure/cef/scale_probe.go +2 -2
| @@ -49,8 +49,8 @@ func cefScaleProbeSnapshot(wv *WebView) cefScaleProbeMetrics { | |||
| 49 | 49 | return m | |
| 50 | 50 | } | |
| 51 | 51 | ||
| 52 | - | func (m cefScaleProbeMetrics) logFields() map[string]interface{} { | |
| 53 | - | return map[string]interface{}{ | |
| 52 | + | func (m cefScaleProbeMetrics) logFields() map[string]any { | |
| 53 | + | return map[string]any{ | |
| 54 | 54 | "surface_width": m.SurfaceWidth, | |
| 55 | 55 | "surface_height": m.SurfaceHeight, | |
| 56 | 56 | "surface_scale": roundedScale(m.SurfaceScale), | |
| @@ -49,8 +49,8 @@ func cefScaleProbeSnapshot(wv *WebView) cefScaleProbeMetrics { | |||||
| 49 | return m | 49 | return m | ||
| 50 | } | 50 | } | ||
| 51 | 51 | ||||
| 52 | - | func (m cefScaleProbeMetrics) logFields() map[string]interface{} { | 52 | + | func (m cefScaleProbeMetrics) logFields() map[string]any { |
| 53 | - | return map[string]interface{}{ | 53 | + | return map[string]any{ |
| 54 | "surface_width": m.SurfaceWidth, | 54 | "surface_width": m.SurfaceWidth, | ||
| 55 | "surface_height": m.SurfaceHeight, | 55 | "surface_height": m.SurfaceHeight, | ||
| 56 | "surface_scale": roundedScale(m.SurfaceScale), | 56 | "surface_scale": roundedScale(m.SurfaceScale), | ||
M internal/infrastructure/cef/scheme_handler.go +4 -11
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | 10 | "html" | |
| 11 | 11 | "io" | |
| 12 | 12 | "io/fs" | |
| 13 | + | "maps" | |
| 13 | 14 | "net/http" | |
| 14 | 15 | "net/url" | |
| 15 | 16 | "path" | |
| @@ -780,9 +781,7 @@ func apiResponseHeaders(extra map[string]string) map[string]string { | |||
| 780 | 781 | "Access-Control-Max-Age": "86400", | |
| 781 | 782 | "Cache-Control": "no-store", | |
| 782 | 783 | } | |
| 783 | - | for name, value := range extra { | |
| 784 | - | headers[name] = value | |
| 785 | - | } | |
| 784 | + | maps.Copy(headers, extra) | |
| 786 | 785 | return headers | |
| 787 | 786 | } | |
| 788 | 787 | ||
| @@ -942,10 +941,7 @@ func (rh *faviconResourceHandler) Read(dataOut unsafe.Pointer, bytesToRead int32 | |||
| 942 | 941 | return 0 | |
| 943 | 942 | } | |
| 944 | 943 | remaining := len(rh.data) - rh.offset | |
| 945 | - | toRead := int(bytesToRead) | |
| 946 | - | if toRead > remaining { | |
| 947 | - | toRead = remaining | |
| 948 | - | } | |
| 944 | + | toRead := min(int(bytesToRead), remaining) | |
| 949 | 945 | dst := unsafe.Slice((*byte)(dataOut), toRead) | |
| 950 | 946 | copy(dst, rh.data[rh.offset:rh.offset+toRead]) | |
| 951 | 947 | rh.offset += toRead | |
| @@ -1017,10 +1013,7 @@ func (rh *staticResourceHandler) Read( | |||
| 1017 | 1013 | } | |
| 1018 | 1014 | ||
| 1019 | 1015 | remaining := len(rh.data) - rh.offset | |
| 1020 | - | toRead := int(bytesToRead) | |
| 1021 | - | if toRead > remaining { | |
| 1022 | - | toRead = remaining | |
| 1023 | - | } | |
| 1016 | + | toRead := min(int(bytesToRead), remaining) | |
| 1024 | 1017 | ||
| 1025 | 1018 | // Copy data to the output buffer. | |
| 1026 | 1019 | dst := unsafe.Slice((*byte)(dataOut), toRead) | |
| @@ -10,6 +10,7 @@ import ( | |||||
| 10 | "html" | 10 | "html" | ||
| 11 | "io" | 11 | "io" | ||
| 12 | "io/fs" | 12 | "io/fs" | ||
| 13 | + | "maps" | |||
| 13 | "net/http" | 14 | "net/http" | ||
| 14 | "net/url" | 15 | "net/url" | ||
| 15 | "path" | 16 | "path" | ||
| @@ -780,9 +781,7 @@ func apiResponseHeaders(extra map[string]string) map[string]string { | |||||
| 780 | "Access-Control-Max-Age": "86400", | 781 | "Access-Control-Max-Age": "86400", | ||
| 781 | "Cache-Control": "no-store", | 782 | "Cache-Control": "no-store", | ||
| 782 | } | 783 | } | ||
| 783 | - | for name, value := range extra { | 784 | + | maps.Copy(headers, extra) |
| 784 | - | headers[name] = value | |||
| 785 | - | } | |||
| 786 | return headers | 785 | return headers | ||
| 787 | } | 786 | } | ||
| 788 | 787 | ||||
| @@ -942,10 +941,7 @@ func (rh *faviconResourceHandler) Read(dataOut unsafe.Pointer, bytesToRead int32 | |||||
| 942 | return 0 | 941 | return 0 | ||
| 943 | } | 942 | } | ||
| 944 | remaining := len(rh.data) - rh.offset | 943 | remaining := len(rh.data) - rh.offset | ||
| 945 | - | toRead := int(bytesToRead) | 944 | + | toRead := min(int(bytesToRead), remaining) |
| 946 | - | if toRead > remaining { | |||
| 947 | - | toRead = remaining | |||
| 948 | - | } | |||
| 949 | dst := unsafe.Slice((*byte)(dataOut), toRead) | 945 | dst := unsafe.Slice((*byte)(dataOut), toRead) | ||
| 950 | copy(dst, rh.data[rh.offset:rh.offset+toRead]) | 946 | copy(dst, rh.data[rh.offset:rh.offset+toRead]) | ||
| 951 | rh.offset += toRead | 947 | rh.offset += toRead | ||
| @@ -1017,10 +1013,7 @@ func (rh *staticResourceHandler) Read( | |||||
| 1017 | } | 1013 | } | ||
| 1018 | 1014 | ||||
| 1019 | remaining := len(rh.data) - rh.offset | 1015 | remaining := len(rh.data) - rh.offset | ||
| 1020 | - | toRead := int(bytesToRead) | 1016 | + | toRead := min(int(bytesToRead), remaining) |
| 1021 | - | if toRead > remaining { | |||
| 1022 | - | toRead = remaining | |||
| 1023 | - | } | |||
| 1024 | 1017 | ||||
| 1025 | // Copy data to the output buffer. | 1018 | // Copy data to the output buffer. | ||
| 1026 | dst := unsafe.Slice((*byte)(dataOut), toRead) | 1019 | dst := unsafe.Slice((*byte)(dataOut), toRead) | ||
M internal/infrastructure/colorscheme/resolver_test.go +9 -13
| @@ -355,29 +355,25 @@ func TestResolver_ConcurrentAccess(_ *testing.T) { | |||
| 355 | 355 | const goroutines = 10 | |
| 356 | 356 | ||
| 357 | 357 | // Concurrent Resolve calls | |
| 358 | - | for i := 0; i < goroutines; i++ { | |
| 359 | - | wg.Add(1) | |
| 360 | - | go func() { | |
| 361 | - | defer wg.Done() | |
| 362 | - | for j := 0; j < 100; j++ { | |
| 358 | + | for range goroutines { | |
| 359 | + | wg.Go(func() { | |
| 360 | + | for range 100 { | |
| 363 | 361 | resolver.Resolve() | |
| 364 | 362 | } | |
| 365 | - | }() | |
| 363 | + | }) | |
| 366 | 364 | } | |
| 367 | 365 | ||
| 368 | 366 | // Concurrent Refresh calls | |
| 369 | - | for i := 0; i < goroutines; i++ { | |
| 370 | - | wg.Add(1) | |
| 371 | - | go func() { | |
| 372 | - | defer wg.Done() | |
| 373 | - | for j := 0; j < 100; j++ { | |
| 367 | + | for range goroutines { | |
| 368 | + | wg.Go(func() { | |
| 369 | + | for range 100 { | |
| 374 | 370 | resolver.Refresh() | |
| 375 | 371 | } | |
| 376 | - | }() | |
| 372 | + | }) | |
| 377 | 373 | } | |
| 378 | 374 | ||
| 379 | 375 | // Concurrent RegisterDetector calls | |
| 380 | - | for i := 0; i < goroutines; i++ { | |
| 376 | + | for i := range goroutines { | |
| 381 | 377 | wg.Add(1) | |
| 382 | 378 | go func(id int) { | |
| 383 | 379 | defer wg.Done() | |
| @@ -355,29 +355,25 @@ func TestResolver_ConcurrentAccess(_ *testing.T) { | |||||
| 355 | const goroutines = 10 | 355 | const goroutines = 10 | ||
| 356 | 356 | ||||
| 357 | // Concurrent Resolve calls | 357 | // Concurrent Resolve calls | ||
| 358 | - | for i := 0; i < goroutines; i++ { | 358 | + | for range goroutines { |
| 359 | - | wg.Add(1) | 359 | + | wg.Go(func() { |
| 360 | - | go func() { | 360 | + | for range 100 { |
| 361 | - | defer wg.Done() | |||
| 362 | - | for j := 0; j < 100; j++ { | |||
| 363 | resolver.Resolve() | 361 | resolver.Resolve() | ||
| 364 | } | 362 | } | ||
| 365 | - | }() | 363 | + | }) |
| 366 | } | 364 | } | ||
| 367 | 365 | ||||
| 368 | // Concurrent Refresh calls | 366 | // Concurrent Refresh calls | ||
| 369 | - | for i := 0; i < goroutines; i++ { | 367 | + | for range goroutines { |
| 370 | - | wg.Add(1) | 368 | + | wg.Go(func() { |
| 371 | - | go func() { | 369 | + | for range 100 { |
| 372 | - | defer wg.Done() | |||
| 373 | - | for j := 0; j < 100; j++ { | |||
| 374 | resolver.Refresh() | 370 | resolver.Refresh() | ||
| 375 | } | 371 | } | ||
| 376 | - | }() | 372 | + | }) |
| 377 | } | 373 | } | ||
| 378 | 374 | ||||
| 379 | // Concurrent RegisterDetector calls | 375 | // Concurrent RegisterDetector calls | ||
| 380 | - | for i := 0; i < goroutines; i++ { | 376 | + | for i := range goroutines { |
| 381 | wg.Add(1) | 377 | wg.Add(1) | ||
| 382 | go func(id int) { | 378 | go func(id int) { | ||
| 383 | defer wg.Done() | 379 | defer wg.Done() | ||
M internal/infrastructure/config/migrate.go +3 -6
| @@ -384,10 +384,7 @@ func keysAreSimilar(oldKey, newKey string) bool { | |||
| 384 | 384 | } | |
| 385 | 385 | ||
| 386 | 386 | // If most tokens match, consider similar | |
| 387 | - | minTokens := len(oldTokens) | |
| 388 | - | if len(newTokens) < minTokens { | |
| 389 | - | minTokens = len(newTokens) | |
| 390 | - | } | |
| 387 | + | minTokens := min(len(newTokens), len(oldTokens)) | |
| 391 | 388 | return matches >= minTokens-1 && matches > 0 | |
| 392 | 389 | } | |
| 393 | 390 | ||
| @@ -652,8 +649,8 @@ func (m *Migrator) defaultValueForKey(key string) any { | |||
| 652 | 649 | } | |
| 653 | 650 | ||
| 654 | 651 | for _, actionMap := range m.defaultActionMaps() { | |
| 655 | - | if strings.HasPrefix(key, actionMap.key+".") { | |
| 656 | - | actionName := strings.TrimPrefix(key, actionMap.key+".") | |
| 652 | + | if after, ok := strings.CutPrefix(key, actionMap.key+"."); ok { | |
| 653 | + | actionName := after | |
| 657 | 654 | if action, ok := actionMap.actions[actionName]; ok { | |
| 658 | 655 | return action | |
| 659 | 656 | } | |
| @@ -384,10 +384,7 @@ func keysAreSimilar(oldKey, newKey string) bool { | |||||
| 384 | } | 384 | } | ||
| 385 | 385 | ||||
| 386 | // If most tokens match, consider similar | 386 | // If most tokens match, consider similar | ||
| 387 | - | minTokens := len(oldTokens) | 387 | + | minTokens := min(len(newTokens), len(oldTokens)) |
| 388 | - | if len(newTokens) < minTokens { | |||
| 389 | - | minTokens = len(newTokens) | |||
| 390 | - | } | |||
| 391 | return matches >= minTokens-1 && matches > 0 | 388 | return matches >= minTokens-1 && matches > 0 | ||
| 392 | } | 389 | } | ||
| 393 | 390 | ||||
| @@ -652,8 +649,8 @@ func (m *Migrator) defaultValueForKey(key string) any { | |||||
| 652 | } | 649 | } | ||
| 653 | 650 | ||||
| 654 | for _, actionMap := range m.defaultActionMaps() { | 651 | for _, actionMap := range m.defaultActionMaps() { | ||
| 655 | - | if strings.HasPrefix(key, actionMap.key+".") { | 652 | + | if after, ok := strings.CutPrefix(key, actionMap.key+"."); ok { |
| 656 | - | actionName := strings.TrimPrefix(key, actionMap.key+".") | 653 | + | actionName := after |
| 657 | if action, ok := actionMap.actions[actionName]; ok { | 654 | if action, ok := actionMap.actions[actionName]; ok { | ||
| 658 | return action | 655 | return action | ||
| 659 | } | 656 | } | ||
M internal/infrastructure/config/profile_test.go +1 -7
| @@ -108,13 +108,7 @@ func TestResolvePerformanceProfile_Max_NoHardware(t *testing.T) { | |||
| 108 | 108 | ||
| 109 | 109 | // Max without hardware info should use fallback values | |
| 110 | 110 | // CPU threads: NumCPU/2 (assuming hyperthreading), clamped to [4, 8] | |
| 111 | - | expectedCPUThreads := runtime.NumCPU() / 2 | |
| 112 | - | if expectedCPUThreads < 4 { | |
| 113 | - | expectedCPUThreads = 4 | |
| 114 | - | } | |
| 115 | - | if expectedCPUThreads > 8 { | |
| 116 | - | expectedCPUThreads = 8 | |
| 117 | - | } | |
| 111 | + | expectedCPUThreads := min(max(runtime.NumCPU()/2, 4), 8) | |
| 118 | 112 | if result.SkiaCPUPaintingThreads != expectedCPUThreads { | |
| 119 | 113 | t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads) | |
| 120 | 114 | } | |
| @@ -108,13 +108,7 @@ func TestResolvePerformanceProfile_Max_NoHardware(t *testing.T) { | |||||
| 108 | 108 | ||||
| 109 | // Max without hardware info should use fallback values | 109 | // Max without hardware info should use fallback values | ||
| 110 | // CPU threads: NumCPU/2 (assuming hyperthreading), clamped to [4, 8] | 110 | // CPU threads: NumCPU/2 (assuming hyperthreading), clamped to [4, 8] | ||
| 111 | - | expectedCPUThreads := runtime.NumCPU() / 2 | 111 | + | expectedCPUThreads := min(max(runtime.NumCPU()/2, 4), 8) |
| 112 | - | if expectedCPUThreads < 4 { | |||
| 113 | - | expectedCPUThreads = 4 | |||
| 114 | - | } | |||
| 115 | - | if expectedCPUThreads > 8 { | |||
| 116 | - | expectedCPUThreads = 8 | |||
| 117 | - | } | |||
| 118 | if result.SkiaCPUPaintingThreads != expectedCPUThreads { | 112 | if result.SkiaCPUPaintingThreads != expectedCPUThreads { | ||
| 119 | t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads) | 113 | t.Errorf("expected SkiaCPUPaintingThreads=%d, got %d", expectedCPUThreads, result.SkiaCPUPaintingThreads) | ||
| 120 | } | 114 | } | ||
M internal/infrastructure/deps/env.go +1 -1
| @@ -107,7 +107,7 @@ func prependPathList(existing string, values ...string) string { | |||
| 107 | 107 | } | |
| 108 | 108 | ||
| 109 | 109 | if existing != "" { | |
| 110 | - | for _, v := range strings.Split(existing, ":") { | |
| 110 | + | for v := range strings.SplitSeq(existing, ":") { | |
| 111 | 111 | add(v) | |
| 112 | 112 | } | |
| 113 | 113 | } | |
| @@ -107,7 +107,7 @@ func prependPathList(existing string, values ...string) string { | |||||
| 107 | } | 107 | } | ||
| 108 | 108 | ||||
| 109 | if existing != "" { | 109 | if existing != "" { | ||
| 110 | - | for _, v := range strings.Split(existing, ":") { | 110 | + | for v := range strings.SplitSeq(existing, ":") { |
| 111 | add(v) | 111 | add(v) | ||
| 112 | } | 112 | } | ||
| 113 | } | 113 | } | ||
M internal/infrastructure/desktop/adapter_test.go +1 -1
| @@ -144,7 +144,7 @@ func readEnvFile(t *testing.T, path string) map[string]string { | |||
| 144 | 144 | data, err := os.ReadFile(path) | |
| 145 | 145 | if err == nil { | |
| 146 | 146 | env := make(map[string]string) | |
| 147 | - | for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { | |
| 147 | + | for line := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") { | |
| 148 | 148 | if line == "" { | |
| 149 | 149 | continue | |
| 150 | 150 | } | |
| @@ -144,7 +144,7 @@ func readEnvFile(t *testing.T, path string) map[string]string { | |||||
| 144 | data, err := os.ReadFile(path) | 144 | data, err := os.ReadFile(path) | ||
| 145 | if err == nil { | 145 | if err == nil { | ||
| 146 | env := make(map[string]string) | 146 | env := make(map[string]string) | ||
| 147 | - | for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { | 147 | + | for line := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") { |
| 148 | if line == "" { | 148 | if line == "" { | ||
| 149 | continue | 149 | continue | ||
| 150 | } | 150 | } | ||
M internal/infrastructure/desktop/browser_launch_relay_test.go +9 -18
| @@ -127,8 +127,7 @@ func TestBrowserLaunchRelay_SameProfileAndEngine_UsesSameSocket(t *testing.T) { | |||
| 127 | 127 | clientRelay := NewBrowserLaunchRelay(ipc) | |
| 128 | 128 | ||
| 129 | 129 | received := make(chan string, 1) | |
| 130 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 131 | - | defer cancel() | |
| 130 | + | ctx := t.Context() | |
| 132 | 131 | ||
| 133 | 132 | closer, err := listenerRelay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | |
| 134 | 133 | received <- url | |
| @@ -229,8 +228,7 @@ func TestBrowserLaunchRelay_DeliverOpenFreshWindow_RoundTrip(t *testing.T) { | |||
| 229 | 228 | relay := NewBrowserLaunchRelay(ipc) | |
| 230 | 229 | ||
| 231 | 230 | received := make(chan string, 1) | |
| 232 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 233 | - | defer cancel() | |
| 231 | + | ctx := t.Context() | |
| 234 | 232 | ||
| 235 | 233 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | |
| 236 | 234 | received <- url | |
| @@ -267,8 +265,7 @@ func TestBrowserLaunchRelay_SecondListenWhileLiveFailsWithoutReplacingListener(t | |||
| 267 | 265 | relay := NewBrowserLaunchRelay(ipc) | |
| 268 | 266 | ||
| 269 | 267 | firstReceived := make(chan string, 1) | |
| 270 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 271 | - | defer cancel() | |
| 268 | + | ctx := t.Context() | |
| 272 | 269 | ||
| 273 | 270 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | |
| 274 | 271 | firstReceived <- url | |
| @@ -302,8 +299,7 @@ func TestBrowserLaunchRelay_RebindsStaleSocketPath(t *testing.T) { | |||
| 302 | 299 | require.NoError(t, os.WriteFile(ipc.BrowserLaunchSocket, []byte("stale"), 0o600)) | |
| 303 | 300 | ||
| 304 | 301 | received := make(chan string, 1) | |
| 305 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 306 | - | defer cancel() | |
| 302 | + | ctx := t.Context() | |
| 307 | 303 | ||
| 308 | 304 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | |
| 309 | 305 | received <- url | |
| @@ -331,8 +327,7 @@ func TestBrowserLaunchRelay_RejectsMalformedPayloads(t *testing.T) { | |||
| 331 | 327 | relay := NewBrowserLaunchRelay(ipc) | |
| 332 | 328 | ||
| 333 | 329 | received := make(chan string, 1) | |
| 334 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 335 | - | defer cancel() | |
| 330 | + | ctx := t.Context() | |
| 336 | 331 | ||
| 337 | 332 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | |
| 338 | 333 | received <- url | |
| @@ -367,8 +362,7 @@ func TestBrowserLaunchRelay_CloseRemovesSocketPath(t *testing.T) { | |||
| 367 | 362 | ipc := testIPC(shortTempDir(t)) | |
| 368 | 363 | relay := NewBrowserLaunchRelay(ipc) | |
| 369 | 364 | ||
| 370 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 371 | - | defer cancel() | |
| 365 | + | ctx := t.Context() | |
| 372 | 366 | ||
| 373 | 367 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(context.Context, string) error { return nil })) | |
| 374 | 368 | require.NoError(t, err) | |
| @@ -486,8 +480,7 @@ func TestBrowserLaunchRelay_AcknowledgesBeforeOpenerReturns(t *testing.T) { | |||
| 486 | 480 | ||
| 487 | 481 | started := make(chan struct{}) | |
| 488 | 482 | release := make(chan struct{}) | |
| 489 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 490 | - | defer cancel() | |
| 483 | + | ctx := t.Context() | |
| 491 | 484 | ||
| 492 | 485 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, _ string) error { | |
| 493 | 486 | close(started) | |
| @@ -524,8 +517,7 @@ func TestBrowserLaunchRelay_AcknowledgesAcceptedEvenWhenOpenerReturnsLateError(t | |||
| 524 | 517 | relay := NewBrowserLaunchRelay(ipc) | |
| 525 | 518 | ||
| 526 | 519 | started := make(chan struct{}) | |
| 527 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 528 | - | defer cancel() | |
| 520 | + | ctx := t.Context() | |
| 529 | 521 | ||
| 530 | 522 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, _ string) error { | |
| 531 | 523 | close(started) | |
| @@ -560,8 +552,7 @@ func TestBrowserLaunchRelay_SilentClientDoesNotStallListener(t *testing.T) { | |||
| 560 | 552 | relay := NewBrowserLaunchRelay(ipc) | |
| 561 | 553 | ||
| 562 | 554 | received := make(chan string, 1) | |
| 563 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 564 | - | defer cancel() | |
| 555 | + | ctx := t.Context() | |
| 565 | 556 | ||
| 566 | 557 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | |
| 567 | 558 | received <- url | |
| @@ -127,8 +127,7 @@ func TestBrowserLaunchRelay_SameProfileAndEngine_UsesSameSocket(t *testing.T) { | |||||
| 127 | clientRelay := NewBrowserLaunchRelay(ipc) | 127 | clientRelay := NewBrowserLaunchRelay(ipc) | ||
| 128 | 128 | ||||
| 129 | received := make(chan string, 1) | 129 | received := make(chan string, 1) | ||
| 130 | - | ctx, cancel := context.WithCancel(context.Background()) | 130 | + | ctx := t.Context() |
| 131 | - | defer cancel() | |||
| 132 | 131 | ||||
| 133 | closer, err := listenerRelay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | 132 | closer, err := listenerRelay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | ||
| 134 | received <- url | 133 | received <- url | ||
| @@ -229,8 +228,7 @@ func TestBrowserLaunchRelay_DeliverOpenFreshWindow_RoundTrip(t *testing.T) { | |||||
| 229 | relay := NewBrowserLaunchRelay(ipc) | 228 | relay := NewBrowserLaunchRelay(ipc) | ||
| 230 | 229 | ||||
| 231 | received := make(chan string, 1) | 230 | received := make(chan string, 1) | ||
| 232 | - | ctx, cancel := context.WithCancel(context.Background()) | 231 | + | ctx := t.Context() |
| 233 | - | defer cancel() | |||
| 234 | 232 | ||||
| 235 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | 233 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | ||
| 236 | received <- url | 234 | received <- url | ||
| @@ -267,8 +265,7 @@ func TestBrowserLaunchRelay_SecondListenWhileLiveFailsWithoutReplacingListener(t | |||||
| 267 | relay := NewBrowserLaunchRelay(ipc) | 265 | relay := NewBrowserLaunchRelay(ipc) | ||
| 268 | 266 | ||||
| 269 | firstReceived := make(chan string, 1) | 267 | firstReceived := make(chan string, 1) | ||
| 270 | - | ctx, cancel := context.WithCancel(context.Background()) | 268 | + | ctx := t.Context() |
| 271 | - | defer cancel() | |||
| 272 | 269 | ||||
| 273 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | 270 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | ||
| 274 | firstReceived <- url | 271 | firstReceived <- url | ||
| @@ -302,8 +299,7 @@ func TestBrowserLaunchRelay_RebindsStaleSocketPath(t *testing.T) { | |||||
| 302 | require.NoError(t, os.WriteFile(ipc.BrowserLaunchSocket, []byte("stale"), 0o600)) | 299 | require.NoError(t, os.WriteFile(ipc.BrowserLaunchSocket, []byte("stale"), 0o600)) | ||
| 303 | 300 | ||||
| 304 | received := make(chan string, 1) | 301 | received := make(chan string, 1) | ||
| 305 | - | ctx, cancel := context.WithCancel(context.Background()) | 302 | + | ctx := t.Context() |
| 306 | - | defer cancel() | |||
| 307 | 303 | ||||
| 308 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | 304 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | ||
| 309 | received <- url | 305 | received <- url | ||
| @@ -331,8 +327,7 @@ func TestBrowserLaunchRelay_RejectsMalformedPayloads(t *testing.T) { | |||||
| 331 | relay := NewBrowserLaunchRelay(ipc) | 327 | relay := NewBrowserLaunchRelay(ipc) | ||
| 332 | 328 | ||||
| 333 | received := make(chan string, 1) | 329 | received := make(chan string, 1) | ||
| 334 | - | ctx, cancel := context.WithCancel(context.Background()) | 330 | + | ctx := t.Context() |
| 335 | - | defer cancel() | |||
| 336 | 331 | ||||
| 337 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | 332 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | ||
| 338 | received <- url | 333 | received <- url | ||
| @@ -367,8 +362,7 @@ func TestBrowserLaunchRelay_CloseRemovesSocketPath(t *testing.T) { | |||||
| 367 | ipc := testIPC(shortTempDir(t)) | 362 | ipc := testIPC(shortTempDir(t)) | ||
| 368 | relay := NewBrowserLaunchRelay(ipc) | 363 | relay := NewBrowserLaunchRelay(ipc) | ||
| 369 | 364 | ||||
| 370 | - | ctx, cancel := context.WithCancel(context.Background()) | 365 | + | ctx := t.Context() |
| 371 | - | defer cancel() | |||
| 372 | 366 | ||||
| 373 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(context.Context, string) error { return nil })) | 367 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(context.Context, string) error { return nil })) | ||
| 374 | require.NoError(t, err) | 368 | require.NoError(t, err) | ||
| @@ -486,8 +480,7 @@ func TestBrowserLaunchRelay_AcknowledgesBeforeOpenerReturns(t *testing.T) { | |||||
| 486 | 480 | ||||
| 487 | started := make(chan struct{}) | 481 | started := make(chan struct{}) | ||
| 488 | release := make(chan struct{}) | 482 | release := make(chan struct{}) | ||
| 489 | - | ctx, cancel := context.WithCancel(context.Background()) | 483 | + | ctx := t.Context() |
| 490 | - | defer cancel() | |||
| 491 | 484 | ||||
| 492 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, _ string) error { | 485 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, _ string) error { | ||
| 493 | close(started) | 486 | close(started) | ||
| @@ -524,8 +517,7 @@ func TestBrowserLaunchRelay_AcknowledgesAcceptedEvenWhenOpenerReturnsLateError(t | |||||
| 524 | relay := NewBrowserLaunchRelay(ipc) | 517 | relay := NewBrowserLaunchRelay(ipc) | ||
| 525 | 518 | ||||
| 526 | started := make(chan struct{}) | 519 | started := make(chan struct{}) | ||
| 527 | - | ctx, cancel := context.WithCancel(context.Background()) | 520 | + | ctx := t.Context() |
| 528 | - | defer cancel() | |||
| 529 | 521 | ||||
| 530 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, _ string) error { | 522 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, _ string) error { | ||
| 531 | close(started) | 523 | close(started) | ||
| @@ -560,8 +552,7 @@ func TestBrowserLaunchRelay_SilentClientDoesNotStallListener(t *testing.T) { | |||||
| 560 | relay := NewBrowserLaunchRelay(ipc) | 552 | relay := NewBrowserLaunchRelay(ipc) | ||
| 561 | 553 | ||||
| 562 | received := make(chan string, 1) | 554 | received := make(chan string, 1) | ||
| 563 | - | ctx, cancel := context.WithCancel(context.Background()) | 555 | + | ctx := t.Context() |
| 564 | - | defer cancel() | |||
| 565 | 556 | ||||
| 566 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | 557 | closer, err := relay.Listen(ctx, browserWindowOpenerFunc(func(_ context.Context, url string) error { | ||
| 567 | received <- url | 558 | received <- url | ||
M internal/infrastructure/env/hardware_survey.go +2 -2
| @@ -276,9 +276,9 @@ func (*HardwareSurveyor) detectGPUName(ctx context.Context, cardPath string) str | |||
| 276 | 276 | scanner := bufio.NewScanner(file) | |
| 277 | 277 | for scanner.Scan() { | |
| 278 | 278 | line := scanner.Text() | |
| 279 | - | if strings.HasPrefix(line, "PCI_SLOT_NAME=") { | |
| 279 | + | if after, ok := strings.CutPrefix(line, "PCI_SLOT_NAME="); ok { | |
| 280 | 280 | // This gives us the PCI address, useful for debugging | |
| 281 | - | log.Debug().Str("pci_slot", strings.TrimPrefix(line, "PCI_SLOT_NAME=")).Msg("GPU PCI slot") | |
| 281 | + | log.Debug().Str("pci_slot", after).Msg("GPU PCI slot") | |
| 282 | 282 | } | |
| 283 | 283 | } | |
| 284 | 284 | _ = file.Close() | |
| @@ -276,9 +276,9 @@ func (*HardwareSurveyor) detectGPUName(ctx context.Context, cardPath string) str | |||||
| 276 | scanner := bufio.NewScanner(file) | 276 | scanner := bufio.NewScanner(file) | ||
| 277 | for scanner.Scan() { | 277 | for scanner.Scan() { | ||
| 278 | line := scanner.Text() | 278 | line := scanner.Text() | ||
| 279 | - | if strings.HasPrefix(line, "PCI_SLOT_NAME=") { | 279 | + | if after, ok := strings.CutPrefix(line, "PCI_SLOT_NAME="); ok { |
| 280 | // This gives us the PCI address, useful for debugging | 280 | // This gives us the PCI address, useful for debugging | ||
| 281 | - | log.Debug().Str("pci_slot", strings.TrimPrefix(line, "PCI_SLOT_NAME=")).Msg("GPU PCI slot") | 281 | + | log.Debug().Str("pci_slot", after).Msg("GPU PCI slot") |
| 282 | } | 282 | } | ||
| 283 | } | 283 | } | ||
| 284 | _ = file.Close() | 284 | _ = file.Close() | ||
M internal/infrastructure/env/manager.go +3 -7
| @@ -4,6 +4,7 @@ package env | |||
| 4 | 4 | import ( | |
| 5 | 5 | "context" | |
| 6 | 6 | "fmt" | |
| 7 | + | "maps" | |
| 7 | 8 | "os" | |
| 8 | 9 | "os/exec" | |
| 9 | 10 | "path/filepath" | |
| @@ -118,10 +119,7 @@ func (m *Manager) applyGStreamerEnv(settings RenderingSettings) { | |||
| 118 | 119 | ||
| 119 | 120 | // GStreamer debug level | |
| 120 | 121 | if settings.GStreamerDebugLevel > 0 { | |
| 121 | - | level := settings.GStreamerDebugLevel | |
| 122 | - | if level > 5 { | |
| 123 | - | level = 5 | |
| 124 | - | } | |
| 122 | + | level := min(settings.GStreamerDebugLevel, 5) | |
| 125 | 123 | m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level)) | |
| 126 | 124 | } | |
| 127 | 125 | ||
| @@ -221,9 +219,7 @@ func (m *Manager) setEnv(key, value string) { | |||
| 221 | 219 | // GetAppliedVars returns a copy of applied environment variables. | |
| 222 | 220 | func (m *Manager) GetAppliedVars() map[string]string { | |
| 223 | 221 | result := make(map[string]string, len(m.appliedVars)) | |
| 224 | - | for k, v := range m.appliedVars { | |
| 225 | - | result[k] = v | |
| 226 | - | } | |
| 222 | + | maps.Copy(result, m.appliedVars) | |
| 227 | 223 | return result | |
| 228 | 224 | } | |
| 229 | 225 | ||
| @@ -4,6 +4,7 @@ package env | |||||
| 4 | import ( | 4 | import ( | ||
| 5 | "context" | 5 | "context" | ||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | + | "maps" | |||
| 7 | "os" | 8 | "os" | ||
| 8 | "os/exec" | 9 | "os/exec" | ||
| 9 | "path/filepath" | 10 | "path/filepath" | ||
| @@ -118,10 +119,7 @@ func (m *Manager) applyGStreamerEnv(settings RenderingSettings) { | |||||
| 118 | 119 | ||||
| 119 | // GStreamer debug level | 120 | // GStreamer debug level | ||
| 120 | if settings.GStreamerDebugLevel > 0 { | 121 | if settings.GStreamerDebugLevel > 0 { | ||
| 121 | - | level := settings.GStreamerDebugLevel | 122 | + | level := min(settings.GStreamerDebugLevel, 5) |
| 122 | - | if level > 5 { | |||
| 123 | - | level = 5 | |||
| 124 | - | } | |||
| 125 | m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level)) | 123 | m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level)) | ||
| 126 | } | 124 | } | ||
| 127 | 125 | ||||
| @@ -221,9 +219,7 @@ func (m *Manager) setEnv(key, value string) { | |||||
| 221 | // GetAppliedVars returns a copy of applied environment variables. | 219 | // GetAppliedVars returns a copy of applied environment variables. | ||
| 222 | func (m *Manager) GetAppliedVars() map[string]string { | 220 | func (m *Manager) GetAppliedVars() map[string]string { | ||
| 223 | result := make(map[string]string, len(m.appliedVars)) | 221 | result := make(map[string]string, len(m.appliedVars)) | ||
| 224 | - | for k, v := range m.appliedVars { | 222 | + | maps.Copy(result, m.appliedVars) |
| 225 | - | result[k] = v | |||
| 226 | - | } | |||
| 227 | return result | 223 | return result | ||
| 228 | } | 224 | } | ||
| 229 | 225 | ||||
M internal/infrastructure/favicon/converter.go +3 -3
| @@ -118,7 +118,7 @@ func decodeICO(data []byte) (image.Image, error) { | |||
| 118 | 118 | var best []byte | |
| 119 | 119 | bestBitCount := 0 | |
| 120 | 120 | bestArea := -1 | |
| 121 | - | for i := 0; i < count; i++ { | |
| 121 | + | for i := range count { | |
| 122 | 122 | entry := data[6+i*16 : 6+(i+1)*16] | |
| 123 | 123 | w, h := int(entry[0]), int(entry[1]) | |
| 124 | 124 | if w == 0 { | |
| @@ -192,10 +192,10 @@ func decodeICODIB(data []byte, bitCountHint int) (image.Image, error) { | |||
| 192 | 192 | } | |
| 193 | 193 | img := image.NewNRGBA(image.Rect(0, 0, width, height)) | |
| 194 | 194 | const opaqueAlpha = 255 | |
| 195 | - | for y := 0; y < height; y++ { | |
| 195 | + | for y := range height { | |
| 196 | 196 | srcY := height - 1 - y | |
| 197 | 197 | row := data[pixelOffset+srcY*stride:] | |
| 198 | - | for x := 0; x < width; x++ { | |
| 198 | + | for x := range width { | |
| 199 | 199 | i := x * bytesPerPixel | |
| 200 | 200 | b, g, r := row[i], row[i+1], row[i+2] | |
| 201 | 201 | a := byte(opaqueAlpha) | |
| @@ -118,7 +118,7 @@ func decodeICO(data []byte) (image.Image, error) { | |||||
| 118 | var best []byte | 118 | var best []byte | ||
| 119 | bestBitCount := 0 | 119 | bestBitCount := 0 | ||
| 120 | bestArea := -1 | 120 | bestArea := -1 | ||
| 121 | - | for i := 0; i < count; i++ { | 121 | + | for i := range count { |
| 122 | entry := data[6+i*16 : 6+(i+1)*16] | 122 | entry := data[6+i*16 : 6+(i+1)*16] | ||
| 123 | w, h := int(entry[0]), int(entry[1]) | 123 | w, h := int(entry[0]), int(entry[1]) | ||
| 124 | if w == 0 { | 124 | if w == 0 { | ||
| @@ -192,10 +192,10 @@ func decodeICODIB(data []byte, bitCountHint int) (image.Image, error) { | |||||
| 192 | } | 192 | } | ||
| 193 | img := image.NewNRGBA(image.Rect(0, 0, width, height)) | 193 | img := image.NewNRGBA(image.Rect(0, 0, width, height)) | ||
| 194 | const opaqueAlpha = 255 | 194 | const opaqueAlpha = 255 | ||
| 195 | - | for y := 0; y < height; y++ { | 195 | + | for y := range height { |
| 196 | srcY := height - 1 - y | 196 | srcY := height - 1 - y | ||
| 197 | row := data[pixelOffset+srcY*stride:] | 197 | row := data[pixelOffset+srcY*stride:] | ||
| 198 | - | for x := 0; x < width; x++ { | 198 | + | for x := range width { |
| 199 | i := x * bytesPerPixel | 199 | i := x * bytesPerPixel | ||
| 200 | b, g, r := row[i], row[i+1], row[i+2] | 200 | b, g, r := row[i], row[i+1], row[i+2] | ||
| 201 | a := byte(opaqueAlpha) | 201 | a := byte(opaqueAlpha) | ||
M internal/infrastructure/favicon/converter_test.go +2 -2
| @@ -139,8 +139,8 @@ func testICOWithPayload(t *testing.T, payload []byte, bitCount byte) []byte { | |||
| 139 | 139 | func testPNG(t *testing.T, w, h int) []byte { | |
| 140 | 140 | t.Helper() | |
| 141 | 141 | img := image.NewRGBA(image.Rect(0, 0, w, h)) | |
| 142 | - | for y := 0; y < h; y++ { | |
| 143 | - | for x := 0; x < w; x++ { | |
| 142 | + | for y := range h { | |
| 143 | + | for x := range w { | |
| 144 | 144 | img.Set(x, y, color.RGBA{R: 200, A: 255}) | |
| 145 | 145 | } | |
| 146 | 146 | } | |
| @@ -139,8 +139,8 @@ func testICOWithPayload(t *testing.T, payload []byte, bitCount byte) []byte { | |||||
| 139 | func testPNG(t *testing.T, w, h int) []byte { | 139 | func testPNG(t *testing.T, w, h int) []byte { | ||
| 140 | t.Helper() | 140 | t.Helper() | ||
| 141 | img := image.NewRGBA(image.Rect(0, 0, w, h)) | 141 | img := image.NewRGBA(image.Rect(0, 0, w, h)) | ||
| 142 | - | for y := 0; y < h; y++ { | 142 | + | for y := range h { |
| 143 | - | for x := 0; x < w; x++ { | 143 | + | for x := range w { |
| 144 | img.Set(x, y, color.RGBA{R: 200, A: 255}) | 144 | img.Set(x, y, color.RGBA{R: 200, A: 255}) | ||
| 145 | } | 145 | } | ||
| 146 | } | 146 | } | ||
M internal/infrastructure/filtering/downloader.go +2 -2
| @@ -340,8 +340,8 @@ func validateFilterFilename(filename string) (string, error) { | |||
| 340 | 340 | return "", fmt.Errorf("invalid filter filename %q: URL control characters are not allowed", filename) | |
| 341 | 341 | } | |
| 342 | 342 | ||
| 343 | - | parts := strings.Split(filename, "/") | |
| 344 | - | for _, part := range parts { | |
| 343 | + | parts := strings.SplitSeq(filename, "/") | |
| 344 | + | for part := range parts { | |
| 345 | 345 | if part == "" || part == "." || part == ".." { | |
| 346 | 346 | return "", fmt.Errorf("invalid filter filename %q: path traversal components are not allowed", filename) | |
| 347 | 347 | } | |
| @@ -340,8 +340,8 @@ func validateFilterFilename(filename string) (string, error) { | |||||
| 340 | return "", fmt.Errorf("invalid filter filename %q: URL control characters are not allowed", filename) | 340 | return "", fmt.Errorf("invalid filter filename %q: URL control characters are not allowed", filename) | ||
| 341 | } | 341 | } | ||
| 342 | 342 | ||||
| 343 | - | parts := strings.Split(filename, "/") | 343 | + | parts := strings.SplitSeq(filename, "/") |
| 344 | - | for _, part := range parts { | 344 | + | for part := range parts { |
| 345 | if part == "" || part == "." || part == ".." { | 345 | if part == "" || part == "." || part == ".." { | ||
| 346 | return "", fmt.Errorf("invalid filter filename %q: path traversal components are not allowed", filename) | 346 | return "", fmt.Errorf("invalid filter filename %q: path traversal components are not allowed", filename) | ||
| 347 | } | 347 | } | ||
M internal/infrastructure/filtering/store.go +2 -6
| @@ -3,6 +3,7 @@ package filtering | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "slices" | |
| 6 | 7 | "sync" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/logging" | |
| @@ -248,10 +249,5 @@ func (s *Store) HasCompiledFilter(ctx context.Context, identifier string) bool { | |||
| 248 | 249 | if err != nil { | |
| 249 | 250 | return false | |
| 250 | 251 | } | |
| 251 | - | for _, id := range identifiers { | |
| 252 | - | if id == identifier { | |
| 253 | - | return true | |
| 254 | - | } | |
| 255 | - | } | |
| 256 | - | return false | |
| 252 | + | return slices.Contains(identifiers, identifier) | |
| 257 | 253 | } | |
| @@ -3,6 +3,7 @@ package filtering | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "slices" | |||
| 6 | "sync" | 7 | "sync" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| @@ -248,10 +249,5 @@ func (s *Store) HasCompiledFilter(ctx context.Context, identifier string) bool { | |||||
| 248 | if err != nil { | 249 | if err != nil { | ||
| 249 | return false | 250 | return false | ||
| 250 | } | 251 | } | ||
| 251 | - | for _, id := range identifiers { | 252 | + | return slices.Contains(identifiers, identifier) |
| 252 | - | if id == identifier { | |||
| 253 | - | return true | |||
| 254 | - | } | |||
| 255 | - | } | |||
| 256 | - | return false | |||
| 257 | } | 253 | } | ||
M internal/infrastructure/fonts/detector.go +2 -2
| @@ -180,8 +180,8 @@ func (*Detector) queryFonts(ctx context.Context) ([]string, error) { | |||
| 180 | 180 | } | |
| 181 | 181 | // fc-list may return comma-separated families for fonts with aliases. | |
| 182 | 182 | // e.g., "DejaVu Sans,DejaVu Sans Light" | |
| 183 | - | families := strings.Split(line, ",") | |
| 184 | - | for _, family := range families { | |
| 183 | + | families := strings.SplitSeq(line, ",") | |
| 184 | + | for family := range families { | |
| 185 | 185 | family = strings.TrimSpace(family) | |
| 186 | 186 | if family != "" { | |
| 187 | 187 | fontSet[family] = struct{}{} | |
| @@ -180,8 +180,8 @@ func (*Detector) queryFonts(ctx context.Context) ([]string, error) { | |||||
| 180 | } | 180 | } | ||
| 181 | // fc-list may return comma-separated families for fonts with aliases. | 181 | // fc-list may return comma-separated families for fonts with aliases. | ||
| 182 | // e.g., "DejaVu Sans,DejaVu Sans Light" | 182 | // e.g., "DejaVu Sans,DejaVu Sans Light" | ||
| 183 | - | families := strings.Split(line, ",") | 183 | + | families := strings.SplitSeq(line, ",") |
| 184 | - | for _, family := range families { | 184 | + | for family := range families { |
| 185 | family = strings.TrimSpace(family) | 185 | family = strings.TrimSpace(family) | ||
| 186 | if family != "" { | 186 | if family != "" { | ||
| 187 | fontSet[family] = struct{}{} | 187 | fontSet[family] = struct{}{} | ||
M internal/infrastructure/fonts/detector_test.go +4 -21
| @@ -2,6 +2,7 @@ package fonts | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "slices" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -71,13 +72,7 @@ func TestDetector_SelectBestFont_SansSerif(t *testing.T) { | |||
| 71 | 72 | assert.NotEmpty(t, font) | |
| 72 | 73 | ||
| 73 | 74 | // Should either be a font from the chain or the generic fallback | |
| 74 | - | isFromChain := false | |
| 75 | - | for _, f := range SansSerifFallbackChain() { | |
| 76 | - | if font == f { | |
| 77 | - | isFromChain = true | |
| 78 | - | break | |
| 79 | - | } | |
| 80 | - | } | |
| 75 | + | isFromChain := slices.Contains(SansSerifFallbackChain(), font) | |
| 81 | 76 | if !isFromChain { | |
| 82 | 77 | assert.Equal(t, "sans-serif", font, "should fall back to generic if no chain fonts available") | |
| 83 | 78 | } | |
| @@ -96,13 +91,7 @@ func TestDetector_SelectBestFont_Serif(t *testing.T) { | |||
| 96 | 91 | font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain()) | |
| 97 | 92 | assert.NotEmpty(t, font) | |
| 98 | 93 | ||
| 99 | - | isFromChain := false | |
| 100 | - | for _, f := range SerifFallbackChain() { | |
| 101 | - | if font == f { | |
| 102 | - | isFromChain = true | |
| 103 | - | break | |
| 104 | - | } | |
| 105 | - | } | |
| 94 | + | isFromChain := slices.Contains(SerifFallbackChain(), font) | |
| 106 | 95 | if !isFromChain { | |
| 107 | 96 | assert.Equal(t, "serif", font, "should fall back to generic if no chain fonts available") | |
| 108 | 97 | } | |
| @@ -121,13 +110,7 @@ func TestDetector_SelectBestFont_Monospace(t *testing.T) { | |||
| 121 | 110 | font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain()) | |
| 122 | 111 | assert.NotEmpty(t, font) | |
| 123 | 112 | ||
| 124 | - | isFromChain := false | |
| 125 | - | for _, f := range MonospaceFallbackChain() { | |
| 126 | - | if font == f { | |
| 127 | - | isFromChain = true | |
| 128 | - | break | |
| 129 | - | } | |
| 130 | - | } | |
| 113 | + | isFromChain := slices.Contains(MonospaceFallbackChain(), font) | |
| 131 | 114 | if !isFromChain { | |
| 132 | 115 | assert.Equal(t, "monospace", font, "should fall back to generic if no chain fonts available") | |
| 133 | 116 | } | |
| @@ -2,6 +2,7 @@ package fonts | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | + | "slices" | |||
| 5 | "testing" | 6 | "testing" | ||
| 6 | 7 | ||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -71,13 +72,7 @@ func TestDetector_SelectBestFont_SansSerif(t *testing.T) { | |||||
| 71 | assert.NotEmpty(t, font) | 72 | assert.NotEmpty(t, font) | ||
| 72 | 73 | ||||
| 73 | // Should either be a font from the chain or the generic fallback | 74 | // Should either be a font from the chain or the generic fallback | ||
| 74 | - | isFromChain := false | 75 | + | isFromChain := slices.Contains(SansSerifFallbackChain(), font) |
| 75 | - | for _, f := range SansSerifFallbackChain() { | |||
| 76 | - | if font == f { | |||
| 77 | - | isFromChain = true | |||
| 78 | - | break | |||
| 79 | - | } | |||
| 80 | - | } | |||
| 81 | if !isFromChain { | 76 | if !isFromChain { | ||
| 82 | assert.Equal(t, "sans-serif", font, "should fall back to generic if no chain fonts available") | 77 | assert.Equal(t, "sans-serif", font, "should fall back to generic if no chain fonts available") | ||
| 83 | } | 78 | } | ||
| @@ -96,13 +91,7 @@ func TestDetector_SelectBestFont_Serif(t *testing.T) { | |||||
| 96 | font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain()) | 91 | font := detector.SelectBestFont(ctx, port.FontCategorySerif, SerifFallbackChain()) | ||
| 97 | assert.NotEmpty(t, font) | 92 | assert.NotEmpty(t, font) | ||
| 98 | 93 | ||||
| 99 | - | isFromChain := false | 94 | + | isFromChain := slices.Contains(SerifFallbackChain(), font) |
| 100 | - | for _, f := range SerifFallbackChain() { | |||
| 101 | - | if font == f { | |||
| 102 | - | isFromChain = true | |||
| 103 | - | break | |||
| 104 | - | } | |||
| 105 | - | } | |||
| 106 | if !isFromChain { | 95 | if !isFromChain { | ||
| 107 | assert.Equal(t, "serif", font, "should fall back to generic if no chain fonts available") | 96 | assert.Equal(t, "serif", font, "should fall back to generic if no chain fonts available") | ||
| 108 | } | 97 | } | ||
| @@ -121,13 +110,7 @@ func TestDetector_SelectBestFont_Monospace(t *testing.T) { | |||||
| 121 | font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain()) | 110 | font := detector.SelectBestFont(ctx, port.FontCategoryMonospace, MonospaceFallbackChain()) | ||
| 122 | assert.NotEmpty(t, font) | 111 | assert.NotEmpty(t, font) | ||
| 123 | 112 | ||||
| 124 | - | isFromChain := false | 113 | + | isFromChain := slices.Contains(MonospaceFallbackChain(), font) |
| 125 | - | for _, f := range MonospaceFallbackChain() { | |||
| 126 | - | if font == f { | |||
| 127 | - | isFromChain = true | |||
| 128 | - | break | |||
| 129 | - | } | |||
| 130 | - | } | |||
| 131 | if !isFromChain { | 114 | if !isFromChain { | ||
| 132 | assert.Equal(t, "monospace", font, "should fall back to generic if no chain fonts available") | 115 | assert.Equal(t, "monospace", font, "should fall back to generic if no chain fonts available") | ||
| 133 | } | 116 | } | ||
M internal/infrastructure/media/diagnostics.go +7 -7
| @@ -93,8 +93,8 @@ func (a *Adapter) parseVADecoders(output string, r *port.MediaDiagnosticsResult) | |||
| 93 | 93 | if a == nil { | |
| 94 | 94 | return | |
| 95 | 95 | } | |
| 96 | - | lines := strings.Split(output, "\n") | |
| 97 | - | for _, line := range lines { | |
| 96 | + | lines := strings.SplitSeq(output, "\n") | |
| 97 | + | for line := range lines { | |
| 98 | 98 | line = strings.TrimSpace(line) | |
| 99 | 99 | // Format: " vah264dec: VA-API H.264 Decoder" | |
| 100 | 100 | if strings.HasPrefix(line, "vaav1dec") { | |
| @@ -117,8 +117,8 @@ func (a *Adapter) parseVAAPIDecoders(output string, r *port.MediaDiagnosticsResu | |||
| 117 | 117 | if a == nil { | |
| 118 | 118 | return | |
| 119 | 119 | } | |
| 120 | - | lines := strings.Split(output, "\n") | |
| 121 | - | for _, line := range lines { | |
| 120 | + | lines := strings.SplitSeq(output, "\n") | |
| 121 | + | for line := range lines { | |
| 122 | 122 | line = strings.TrimSpace(line) | |
| 123 | 123 | // Format: " vaapih264dec: VA-API H264 decoder" | |
| 124 | 124 | if strings.HasPrefix(line, "vaapiav1dec") { | |
| @@ -141,8 +141,8 @@ func (a *Adapter) parseNVCodecDecoders(output string, r *port.MediaDiagnosticsRe | |||
| 141 | 141 | if a == nil { | |
| 142 | 142 | return | |
| 143 | 143 | } | |
| 144 | - | lines := strings.Split(output, "\n") | |
| 145 | - | for _, line := range lines { | |
| 144 | + | lines := strings.SplitSeq(output, "\n") | |
| 145 | + | for line := range lines { | |
| 146 | 146 | line = strings.TrimSpace(line) | |
| 147 | 147 | // Format: " nvav1dec: NVDEC AV1 Decoder" | |
| 148 | 148 | if strings.HasPrefix(line, "nvav1dec") { | |
| @@ -189,7 +189,7 @@ func (a *Adapter) checkVAAPI(ctx context.Context, r *port.MediaDiagnosticsResult | |||
| 189 | 189 | r.VAAPIAvailable = true | |
| 190 | 190 | ||
| 191 | 191 | // Parse driver name and version from vainfo output | |
| 192 | - | for _, line := range strings.Split(output, "\n") { | |
| 192 | + | for line := range strings.SplitSeq(output, "\n") { | |
| 193 | 193 | line = strings.TrimSpace(line) | |
| 194 | 194 | ||
| 195 | 195 | // Extract driver from "vainfo: Driver version: ..." | |
| @@ -93,8 +93,8 @@ func (a *Adapter) parseVADecoders(output string, r *port.MediaDiagnosticsResult) | |||||
| 93 | if a == nil { | 93 | if a == nil { | ||
| 94 | return | 94 | return | ||
| 95 | } | 95 | } | ||
| 96 | - | lines := strings.Split(output, "\n") | 96 | + | lines := strings.SplitSeq(output, "\n") |
| 97 | - | for _, line := range lines { | 97 | + | for line := range lines { |
| 98 | line = strings.TrimSpace(line) | 98 | line = strings.TrimSpace(line) | ||
| 99 | // Format: " vah264dec: VA-API H.264 Decoder" | 99 | // Format: " vah264dec: VA-API H.264 Decoder" | ||
| 100 | if strings.HasPrefix(line, "vaav1dec") { | 100 | if strings.HasPrefix(line, "vaav1dec") { | ||
| @@ -117,8 +117,8 @@ func (a *Adapter) parseVAAPIDecoders(output string, r *port.MediaDiagnosticsResu | |||||
| 117 | if a == nil { | 117 | if a == nil { | ||
| 118 | return | 118 | return | ||
| 119 | } | 119 | } | ||
| 120 | - | lines := strings.Split(output, "\n") | 120 | + | lines := strings.SplitSeq(output, "\n") |
| 121 | - | for _, line := range lines { | 121 | + | for line := range lines { |
| 122 | line = strings.TrimSpace(line) | 122 | line = strings.TrimSpace(line) | ||
| 123 | // Format: " vaapih264dec: VA-API H264 decoder" | 123 | // Format: " vaapih264dec: VA-API H264 decoder" | ||
| 124 | if strings.HasPrefix(line, "vaapiav1dec") { | 124 | if strings.HasPrefix(line, "vaapiav1dec") { | ||
| @@ -141,8 +141,8 @@ func (a *Adapter) parseNVCodecDecoders(output string, r *port.MediaDiagnosticsRe | |||||
| 141 | if a == nil { | 141 | if a == nil { | ||
| 142 | return | 142 | return | ||
| 143 | } | 143 | } | ||
| 144 | - | lines := strings.Split(output, "\n") | 144 | + | lines := strings.SplitSeq(output, "\n") |
| 145 | - | for _, line := range lines { | 145 | + | for line := range lines { |
| 146 | line = strings.TrimSpace(line) | 146 | line = strings.TrimSpace(line) | ||
| 147 | // Format: " nvav1dec: NVDEC AV1 Decoder" | 147 | // Format: " nvav1dec: NVDEC AV1 Decoder" | ||
| 148 | if strings.HasPrefix(line, "nvav1dec") { | 148 | if strings.HasPrefix(line, "nvav1dec") { | ||
| @@ -189,7 +189,7 @@ func (a *Adapter) checkVAAPI(ctx context.Context, r *port.MediaDiagnosticsResult | |||||
| 189 | r.VAAPIAvailable = true | 189 | r.VAAPIAvailable = true | ||
| 190 | 190 | ||||
| 191 | // Parse driver name and version from vainfo output | 191 | // Parse driver name and version from vainfo output | ||
| 192 | - | for _, line := range strings.Split(output, "\n") { | 192 | + | for line := range strings.SplitSeq(output, "\n") { |
| 193 | line = strings.TrimSpace(line) | 193 | line = strings.TrimSpace(line) | ||
| 194 | 194 | ||||
| 195 | // Extract driver from "vainfo: Driver version: ..." | 195 | // Extract driver from "vainfo: Driver version: ..." | ||
M internal/infrastructure/persistence/sqlite/favorite_repo.go +1 -4
| @@ -149,10 +149,7 @@ func (r *favoriteRepo) hydrateTags(ctx context.Context, favorites []*entity.Favo | |||
| 149 | 149 | } | |
| 150 | 150 | ||
| 151 | 151 | for start := 0; start < len(ids); start += favoriteTagHydrationBatchSize { | |
| 152 | - | end := start + favoriteTagHydrationBatchSize | |
| 153 | - | if end > len(ids) { | |
| 154 | - | end = len(ids) | |
| 155 | - | } | |
| 152 | + | end := min(start+favoriteTagHydrationBatchSize, len(ids)) | |
| 156 | 153 | rows, err := r.queries.GetTagsForFavorites(ctx, ids[start:end]) | |
| 157 | 154 | if err != nil { | |
| 158 | 155 | return err | |
| @@ -149,10 +149,7 @@ func (r *favoriteRepo) hydrateTags(ctx context.Context, favorites []*entity.Favo | |||||
| 149 | } | 149 | } | ||
| 150 | 150 | ||||
| 151 | for start := 0; start < len(ids); start += favoriteTagHydrationBatchSize { | 151 | for start := 0; start < len(ids); start += favoriteTagHydrationBatchSize { | ||
| 152 | - | end := start + favoriteTagHydrationBatchSize | 152 | + | end := min(start+favoriteTagHydrationBatchSize, len(ids)) |
| 153 | - | if end > len(ids) { | |||
| 154 | - | end = len(ids) | |||
| 155 | - | } | |||
| 156 | rows, err := r.queries.GetTagsForFavorites(ctx, ids[start:end]) | 153 | rows, err := r.queries.GetTagsForFavorites(ctx, ids[start:end]) | ||
| 157 | if err != nil { | 154 | if err != nil { | ||
| 158 | return err | 155 | return err | ||
M internal/infrastructure/persistence/sqlite/history_repo_test.go +5 -5
| @@ -93,10 +93,10 @@ func TestHistoryRepository_GetMostVisited(t *testing.T) { | |||
| 93 | 93 | ||
| 94 | 94 | // Increment visit counts to create different popularities | |
| 95 | 95 | // github: 5 visits, google: 3 visits, example: 1 visit | |
| 96 | - | for i := 0; i < 4; i++ { | |
| 96 | + | for range 4 { | |
| 97 | 97 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com")) | |
| 98 | 98 | } | |
| 99 | - | for i := 0; i < 2; i++ { | |
| 99 | + | for range 2 { | |
| 100 | 100 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://google.com")) | |
| 101 | 101 | } | |
| 102 | 102 | ||
| @@ -315,7 +315,7 @@ func TestHistoryRepository_GetAllMostVisited(t *testing.T) { | |||
| 315 | 315 | require.NoError(t, repo.Save(ctx, entry2)) | |
| 316 | 316 | ||
| 317 | 317 | // Increment github visits | |
| 318 | - | for i := 0; i < 3; i++ { | |
| 318 | + | for range 3 { | |
| 319 | 319 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com")) | |
| 320 | 320 | } | |
| 321 | 321 | ||
| @@ -756,7 +756,7 @@ func TestHistoryRepository_AboutBlank_CappedVisitCount(t *testing.T) { | |||
| 756 | 756 | repo := sqlite.NewHistoryRepository(db) | |
| 757 | 757 | ||
| 758 | 758 | // Save about:blank multiple times - visit count should stay at 1 | |
| 759 | - | for i := 0; i < 5; i++ { | |
| 759 | + | for range 5 { | |
| 760 | 760 | require.NoError(t, repo.Save(ctx, &entity.HistoryEntry{ | |
| 761 | 761 | URL: "about:blank", | |
| 762 | 762 | Title: "New Tab", | |
| @@ -788,7 +788,7 @@ func TestHistoryRepository_AboutBlank_NotDominant(t *testing.T) { | |||
| 788 | 788 | repo := sqlite.NewHistoryRepository(db) | |
| 789 | 789 | ||
| 790 | 790 | // Save about:blank many times | |
| 791 | - | for i := 0; i < 10; i++ { | |
| 791 | + | for range 10 { | |
| 792 | 792 | require.NoError(t, repo.Save(ctx, &entity.HistoryEntry{ | |
| 793 | 793 | URL: "about:blank", | |
| 794 | 794 | Title: "New Tab", | |
| @@ -93,10 +93,10 @@ func TestHistoryRepository_GetMostVisited(t *testing.T) { | |||||
| 93 | 93 | ||||
| 94 | // Increment visit counts to create different popularities | 94 | // Increment visit counts to create different popularities | ||
| 95 | // github: 5 visits, google: 3 visits, example: 1 visit | 95 | // github: 5 visits, google: 3 visits, example: 1 visit | ||
| 96 | - | for i := 0; i < 4; i++ { | 96 | + | for range 4 { |
| 97 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com")) | 97 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com")) | ||
| 98 | } | 98 | } | ||
| 99 | - | for i := 0; i < 2; i++ { | 99 | + | for range 2 { |
| 100 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://google.com")) | 100 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://google.com")) | ||
| 101 | } | 101 | } | ||
| 102 | 102 | ||||
| @@ -315,7 +315,7 @@ func TestHistoryRepository_GetAllMostVisited(t *testing.T) { | |||||
| 315 | require.NoError(t, repo.Save(ctx, entry2)) | 315 | require.NoError(t, repo.Save(ctx, entry2)) | ||
| 316 | 316 | ||||
| 317 | // Increment github visits | 317 | // Increment github visits | ||
| 318 | - | for i := 0; i < 3; i++ { | 318 | + | for range 3 { |
| 319 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com")) | 319 | require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com")) | ||
| 320 | } | 320 | } | ||
| 321 | 321 | ||||
| @@ -756,7 +756,7 @@ func TestHistoryRepository_AboutBlank_CappedVisitCount(t *testing.T) { | |||||
| 756 | repo := sqlite.NewHistoryRepository(db) | 756 | repo := sqlite.NewHistoryRepository(db) | ||
| 757 | 757 | ||||
| 758 | // Save about:blank multiple times - visit count should stay at 1 | 758 | // Save about:blank multiple times - visit count should stay at 1 | ||
| 759 | - | for i := 0; i < 5; i++ { | 759 | + | for range 5 { |
| 760 | require.NoError(t, repo.Save(ctx, &entity.HistoryEntry{ | 760 | require.NoError(t, repo.Save(ctx, &entity.HistoryEntry{ | ||
| 761 | URL: "about:blank", | 761 | URL: "about:blank", | ||
| 762 | Title: "New Tab", | 762 | Title: "New Tab", | ||
| @@ -788,7 +788,7 @@ func TestHistoryRepository_AboutBlank_NotDominant(t *testing.T) { | |||||
| 788 | repo := sqlite.NewHistoryRepository(db) | 788 | repo := sqlite.NewHistoryRepository(db) | ||
| 789 | 789 | ||||
| 790 | // Save about:blank many times | 790 | // Save about:blank many times | ||
| 791 | - | for i := 0; i < 10; i++ { | 791 | + | for range 10 { |
| 792 | require.NoError(t, repo.Save(ctx, &entity.HistoryEntry{ | 792 | require.NoError(t, repo.Save(ctx, &entity.HistoryEntry{ | ||
| 793 | URL: "about:blank", | 793 | URL: "about:blank", | ||
| 794 | Title: "New Tab", | 794 | Title: "New Tab", | ||
M internal/infrastructure/persistence/sqlite/lazy_db_test.go +3 -3
| @@ -58,7 +58,7 @@ func TestLazyDB_ConcurrentAccess(t *testing.T) { | |||
| 58 | 58 | wg.Add(goroutines) | |
| 59 | 59 | ||
| 60 | 60 | results := make(chan *struct { | |
| 61 | - | db interface{} | |
| 61 | + | db any | |
| 62 | 62 | err error | |
| 63 | 63 | }, goroutines) | |
| 64 | 64 | ||
| @@ -67,7 +67,7 @@ func TestLazyDB_ConcurrentAccess(t *testing.T) { | |||
| 67 | 67 | defer wg.Done() | |
| 68 | 68 | db, err := lazy.DB(ctx) | |
| 69 | 69 | results <- &struct { | |
| 70 | - | db interface{} | |
| 70 | + | db any | |
| 71 | 71 | err error | |
| 72 | 72 | }{db, err} | |
| 73 | 73 | }() | |
| @@ -76,7 +76,7 @@ func TestLazyDB_ConcurrentAccess(t *testing.T) { | |||
| 76 | 76 | wg.Wait() | |
| 77 | 77 | close(results) | |
| 78 | 78 | ||
| 79 | - | var firstDB interface{} | |
| 79 | + | var firstDB any | |
| 80 | 80 | for result := range results { | |
| 81 | 81 | require.NoError(t, result.err) | |
| 82 | 82 | require.NotNil(t, result.db) | |
| @@ -58,7 +58,7 @@ func TestLazyDB_ConcurrentAccess(t *testing.T) { | |||||
| 58 | wg.Add(goroutines) | 58 | wg.Add(goroutines) | ||
| 59 | 59 | ||||
| 60 | results := make(chan *struct { | 60 | results := make(chan *struct { | ||
| 61 | - | db interface{} | 61 | + | db any |
| 62 | err error | 62 | err error | ||
| 63 | }, goroutines) | 63 | }, goroutines) | ||
| 64 | 64 | ||||
| @@ -67,7 +67,7 @@ func TestLazyDB_ConcurrentAccess(t *testing.T) { | |||||
| 67 | defer wg.Done() | 67 | defer wg.Done() | ||
| 68 | db, err := lazy.DB(ctx) | 68 | db, err := lazy.DB(ctx) | ||
| 69 | results <- &struct { | 69 | results <- &struct { | ||
| 70 | - | db interface{} | 70 | + | db any |
| 71 | err error | 71 | err error | ||
| 72 | }{db, err} | 72 | }{db, err} | ||
| 73 | }() | 73 | }() | ||
| @@ -76,7 +76,7 @@ func TestLazyDB_ConcurrentAccess(t *testing.T) { | |||||
| 76 | wg.Wait() | 76 | wg.Wait() | ||
| 77 | close(results) | 77 | close(results) | ||
| 78 | 78 | ||||
| 79 | - | var firstDB interface{} | 79 | + | var firstDB any |
| 80 | for result := range results { | 80 | for result := range results { | ||
| 81 | require.NoError(t, result.err) | 81 | require.NoError(t, result.err) | ||
| 82 | require.NotNil(t, result.db) | 82 | require.NotNil(t, result.db) | ||
M internal/infrastructure/persistence/sqlite/lazy_repos.go +1 -1
| @@ -233,7 +233,7 @@ func (r *LazyHistoryRepository) IncrementVisitCountBy(ctx context.Context, url s | |||
| 233 | 233 | if delta > fallbackCap { | |
| 234 | 234 | return fmt.Errorf("visit delta %d exceeds fallback cap %d and repo does not support IncrementVisitCountBy", delta, fallbackCap) | |
| 235 | 235 | } | |
| 236 | - | for i := 0; i < delta; i++ { | |
| 236 | + | for range delta { | |
| 237 | 237 | if err := r.repo.IncrementVisitCount(ctx, url); err != nil { | |
| 238 | 238 | return err | |
| 239 | 239 | } | |
| @@ -233,7 +233,7 @@ func (r *LazyHistoryRepository) IncrementVisitCountBy(ctx context.Context, url s | |||||
| 233 | if delta > fallbackCap { | 233 | if delta > fallbackCap { | ||
| 234 | return fmt.Errorf("visit delta %d exceeds fallback cap %d and repo does not support IncrementVisitCountBy", delta, fallbackCap) | 234 | return fmt.Errorf("visit delta %d exceeds fallback cap %d and repo does not support IncrementVisitCountBy", delta, fallbackCap) | ||
| 235 | } | 235 | } | ||
| 236 | - | for i := 0; i < delta; i++ { | 236 | + | for range delta { |
| 237 | if err := r.repo.IncrementVisitCount(ctx, url); err != nil { | 237 | if err := r.repo.IncrementVisitCount(ctx, url); err != nil { | ||
| 238 | return err | 238 | return err | ||
| 239 | } | 239 | } | ||
M internal/infrastructure/webkit/context_menu.go +1 -1
| @@ -105,7 +105,7 @@ func resolveDataURIImageData(imageURI string) (entity.ImageData, error) { | |||
| 105 | 105 | ||
| 106 | 106 | isBase64 := false | |
| 107 | 107 | mimeType := "" | |
| 108 | - | for _, part := range strings.Split(meta, ";") { | |
| 108 | + | for part := range strings.SplitSeq(meta, ";") { | |
| 109 | 109 | part = strings.TrimSpace(part) | |
| 110 | 110 | if part == "" { | |
| 111 | 111 | continue | |
| @@ -105,7 +105,7 @@ func resolveDataURIImageData(imageURI string) (entity.ImageData, error) { | |||||
| 105 | 105 | ||||
| 106 | isBase64 := false | 106 | isBase64 := false | ||
| 107 | mimeType := "" | 107 | mimeType := "" | ||
| 108 | - | for _, part := range strings.Split(meta, ";") { | 108 | + | for part := range strings.SplitSeq(meta, ";") { |
| 109 | part = strings.TrimSpace(part) | 109 | part = strings.TrimSpace(part) | ||
| 110 | if part == "" { | 110 | if part == "" { | ||
| 111 | continue | 111 | continue | ||
M internal/infrastructure/webkit/context_menu_test.go +2 -5
| @@ -404,11 +404,8 @@ func (r *sizedByteReader) Read(p []byte) (int, error) { | |||
| 404 | 404 | if r.remaining <= 0 { | |
| 405 | 405 | return 0, io.EOF | |
| 406 | 406 | } | |
| 407 | - | n := len(p) | |
| 408 | - | if n > r.remaining { | |
| 409 | - | n = r.remaining | |
| 410 | - | } | |
| 411 | - | for i := 0; i < n; i++ { | |
| 407 | + | n := min(len(p), r.remaining) | |
| 408 | + | for i := range n { | |
| 412 | 409 | p[i] = 'x' | |
| 413 | 410 | } | |
| 414 | 411 | r.remaining -= n | |
| @@ -404,11 +404,8 @@ func (r *sizedByteReader) Read(p []byte) (int, error) { | |||||
| 404 | if r.remaining <= 0 { | 404 | if r.remaining <= 0 { | ||
| 405 | return 0, io.EOF | 405 | return 0, io.EOF | ||
| 406 | } | 406 | } | ||
| 407 | - | n := len(p) | 407 | + | n := min(len(p), r.remaining) |
| 408 | - | if n > r.remaining { | 408 | + | for i := range n { |
| 409 | - | n = r.remaining | |||
| 410 | - | } | |||
| 411 | - | for i := 0; i < n; i++ { | |||
| 412 | p[i] = 'x' | 409 | p[i] = 'x' | ||
| 413 | } | 410 | } | ||
| 414 | r.remaining -= n | 411 | r.remaining -= n | ||
M internal/infrastructure/webkit/messaging.go +1 -1
| @@ -40,7 +40,7 @@ type MessageRouter struct { | |||
| 40 | 40 | baseCtx context.Context | |
| 41 | 41 | ||
| 42 | 42 | mu sync.RWMutex | |
| 43 | - | callbacks []interface{} | |
| 43 | + | callbacks []any | |
| 44 | 44 | signals []uint | |
| 45 | 45 | ||
| 46 | 46 | idMu sync.Mutex | |
| @@ -40,7 +40,7 @@ type MessageRouter struct { | |||||
| 40 | baseCtx context.Context | 40 | baseCtx context.Context | ||
| 41 | 41 | ||||
| 42 | mu sync.RWMutex | 42 | mu sync.RWMutex | ||
| 43 | - | callbacks []interface{} | 43 | + | callbacks []any |
| 44 | signals []uint | 44 | signals []uint | ||
| 45 | 45 | ||||
| 46 | idMu sync.Mutex | 46 | idMu sync.Mutex | ||
M internal/infrastructure/webkit/pool.go +1 -1
| @@ -497,7 +497,7 @@ func (p *WebViewPool) RefreshScripts(ctx context.Context) { | |||
| 497 | 497 | ||
| 498 | 498 | refreshed := 0 | |
| 499 | 499 | refreshLoop: | |
| 500 | - | for i := 0; i < count; i++ { | |
| 500 | + | for range count { | |
| 501 | 501 | select { | |
| 502 | 502 | case wv := <-p.pool: | |
| 503 | 503 | if wv == nil || wv.IsDestroyed() { | |
| @@ -497,7 +497,7 @@ func (p *WebViewPool) RefreshScripts(ctx context.Context) { | |||||
| 497 | 497 | ||||
| 498 | refreshed := 0 | 498 | refreshed := 0 | ||
| 499 | refreshLoop: | 499 | refreshLoop: | ||
| 500 | - | for i := 0; i < count; i++ { | 500 | + | for range count { |
| 501 | select { | 501 | select { | ||
| 502 | case wv := <-p.pool: | 502 | case wv := <-p.pool: | ||
| 503 | if wv == nil || wv.IsDestroyed() { | 503 | if wv == nil || wv.IsDestroyed() { | ||
M internal/infrastructure/webkit/scheme_handler.go +1 -1
| @@ -296,7 +296,7 @@ func isTrustedSystemviewURL(raw string) bool { | |||
| 296 | 296 | ||
| 297 | 297 | func jsonErrorResponse(status int, message string) *SchemeResponse { | |
| 298 | 298 | return &SchemeResponse{ | |
| 299 | - | Data: []byte(fmt.Sprintf(`{"error":%q}`, message)), | |
| 299 | + | Data: fmt.Appendf(nil, `{"error":%q}`, message), | |
| 300 | 300 | ContentType: "application/json", | |
| 301 | 301 | StatusCode: status, | |
| 302 | 302 | } | |
| @@ -296,7 +296,7 @@ func isTrustedSystemviewURL(raw string) bool { | |||||
| 296 | 296 | ||||
| 297 | func jsonErrorResponse(status int, message string) *SchemeResponse { | 297 | func jsonErrorResponse(status int, message string) *SchemeResponse { | ||
| 298 | return &SchemeResponse{ | 298 | return &SchemeResponse{ | ||
| 299 | - | Data: []byte(fmt.Sprintf(`{"error":%q}`, message)), | 299 | + | Data: fmt.Appendf(nil, `{"error":%q}`, message), |
| 300 | ContentType: "application/json", | 300 | ContentType: "application/json", | ||
| 301 | StatusCode: status, | 301 | StatusCode: status, | ||
| 302 | } | 302 | } | ||
M internal/infrastructure/webkit/webview.go +1 -1
| @@ -180,7 +180,7 @@ type WebView struct { | |||
| 180 | 180 | navigationActive atomic.Bool | |
| 181 | 181 | ||
| 182 | 182 | // asyncCallbacks keeps references to async JS callbacks to prevent GC | |
| 183 | - | asyncCallbacks []interface{} | |
| 183 | + | asyncCallbacks []any | |
| 184 | 184 | ||
| 185 | 185 | // runJSErrorStats aggregates repeated non-fatal RunJavaScript errors by domain+signature. | |
| 186 | 186 | runJSErrorStats map[string]runJSErrorStat | |
| @@ -180,7 +180,7 @@ type WebView struct { | |||||
| 180 | navigationActive atomic.Bool | 180 | navigationActive atomic.Bool | ||
| 181 | 181 | ||||
| 182 | // asyncCallbacks keeps references to async JS callbacks to prevent GC | 182 | // asyncCallbacks keeps references to async JS callbacks to prevent GC | ||
| 183 | - | asyncCallbacks []interface{} | 183 | + | asyncCallbacks []any |
| 184 | 184 | ||||
| 185 | // runJSErrorStats aggregates repeated non-fatal RunJavaScript errors by domain+signature. | 185 | // runJSErrorStats aggregates repeated non-fatal RunJavaScript errors by domain+signature. | ||
| 186 | runJSErrorStats map[string]runJSErrorStat | 186 | runJSErrorStats map[string]runJSErrorStat | ||
M internal/ui/app_browser_launch_test.go +7 -16
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "os" | |
| 12 | 12 | "path/filepath" | |
| 13 | 13 | "reflect" | |
| 14 | + | "slices" | |
| 14 | 15 | "strconv" | |
| 15 | 16 | "strings" | |
| 16 | 17 | "testing" | |
| @@ -97,7 +98,7 @@ func testGDKBackendAllows(backend string) bool { | |||
| 97 | 98 | if configured == "" { | |
| 98 | 99 | return true | |
| 99 | 100 | } | |
| 100 | - | for _, candidate := range strings.Split(configured, ",") { | |
| 101 | + | for candidate := range strings.SplitSeq(configured, ",") { | |
| 101 | 102 | if strings.TrimSpace(candidate) == backend { | |
| 102 | 103 | return true | |
| 103 | 104 | } | |
| @@ -117,12 +118,7 @@ func testHasUsableWaylandDisplay() bool { | |||
| 117 | 118 | if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" && !filepath.IsAbs(waylandDisplay) { | |
| 118 | 119 | candidates = append([]string{filepath.Join(runtimeDir, waylandDisplay)}, candidates...) | |
| 119 | 120 | } | |
| 120 | - | for _, candidate := range candidates { | |
| 121 | - | if testPathIsSocket(candidate) { | |
| 122 | - | return true | |
| 123 | - | } | |
| 124 | - | } | |
| 125 | - | return false | |
| 121 | + | return slices.ContainsFunc(candidates, testPathIsSocket) | |
| 126 | 122 | } | |
| 127 | 123 | ||
| 128 | 124 | func testHasUsableX11Display() bool { | |
| @@ -133,8 +129,8 @@ func testHasUsableX11Display() bool { | |||
| 133 | 129 | if display == "" { | |
| 134 | 130 | return false | |
| 135 | 131 | } | |
| 136 | - | if strings.HasPrefix(display, ":") { | |
| 137 | - | displayNum := strings.TrimPrefix(display, ":") | |
| 132 | + | if after, ok := strings.CutPrefix(display, ":"); ok { | |
| 133 | + | displayNum := after | |
| 138 | 134 | displayNum = strings.SplitN(displayNum, ".", 2)[0] | |
| 139 | 135 | if displayNum == "" { | |
| 140 | 136 | return false | |
| @@ -143,12 +139,7 @@ func testHasUsableX11Display() bool { | |||
| 143 | 139 | if fallback := "/tmp/.X11-unix/X" + displayNum; fallback != x11SocketCandidates[0] { | |
| 144 | 140 | x11SocketCandidates = append(x11SocketCandidates, fallback) | |
| 145 | 141 | } | |
| 146 | - | for _, candidate := range x11SocketCandidates { | |
| 147 | - | if testPathIsSocket(candidate) { | |
| 148 | - | return true | |
| 149 | - | } | |
| 150 | - | } | |
| 151 | - | return false | |
| 142 | + | return slices.ContainsFunc(x11SocketCandidates, testPathIsSocket) | |
| 152 | 143 | } | |
| 153 | 144 | return true // TCP display | |
| 154 | 145 | } | |
| @@ -2191,7 +2182,7 @@ func TestApp_HandlePaneFullscreenChanged_ClearsBottomInset(t *testing.T) { | |||
| 2191 | 2182 | } | |
| 2192 | 2183 | ||
| 2193 | 2184 | func TestApp_RemoveBrowserWindowPromotesDeterministicFallbackWithMainWindow(t *testing.T) { | |
| 2194 | - | for i := 0; i < 20; i++ { | |
| 2185 | + | for range 20 { | |
| 2195 | 2186 | removed := &browserWindow{id: "window-z", mainWindow: &window.MainWindow{}} | |
| 2196 | 2187 | nilWindow := &browserWindow{id: "window-a"} | |
| 2197 | 2188 | firstValid := &browserWindow{id: "window-b", mainWindow: &window.MainWindow{}} | |
| @@ -11,6 +11,7 @@ import ( | |||||
| 11 | "os" | 11 | "os" | ||
| 12 | "path/filepath" | 12 | "path/filepath" | ||
| 13 | "reflect" | 13 | "reflect" | ||
| 14 | + | "slices" | |||
| 14 | "strconv" | 15 | "strconv" | ||
| 15 | "strings" | 16 | "strings" | ||
| 16 | "testing" | 17 | "testing" | ||
| @@ -97,7 +98,7 @@ func testGDKBackendAllows(backend string) bool { | |||||
| 97 | if configured == "" { | 98 | if configured == "" { | ||
| 98 | return true | 99 | return true | ||
| 99 | } | 100 | } | ||
| 100 | - | for _, candidate := range strings.Split(configured, ",") { | 101 | + | for candidate := range strings.SplitSeq(configured, ",") { |
| 101 | if strings.TrimSpace(candidate) == backend { | 102 | if strings.TrimSpace(candidate) == backend { | ||
| 102 | return true | 103 | return true | ||
| 103 | } | 104 | } | ||
| @@ -117,12 +118,7 @@ func testHasUsableWaylandDisplay() bool { | |||||
| 117 | if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" && !filepath.IsAbs(waylandDisplay) { | 118 | if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" && !filepath.IsAbs(waylandDisplay) { | ||
| 118 | candidates = append([]string{filepath.Join(runtimeDir, waylandDisplay)}, candidates...) | 119 | candidates = append([]string{filepath.Join(runtimeDir, waylandDisplay)}, candidates...) | ||
| 119 | } | 120 | } | ||
| 120 | - | for _, candidate := range candidates { | 121 | + | return slices.ContainsFunc(candidates, testPathIsSocket) |
| 121 | - | if testPathIsSocket(candidate) { | |||
| 122 | - | return true | |||
| 123 | - | } | |||
| 124 | - | } | |||
| 125 | - | return false | |||
| 126 | } | 122 | } | ||
| 127 | 123 | ||||
| 128 | func testHasUsableX11Display() bool { | 124 | func testHasUsableX11Display() bool { | ||
| @@ -133,8 +129,8 @@ func testHasUsableX11Display() bool { | |||||
| 133 | if display == "" { | 129 | if display == "" { | ||
| 134 | return false | 130 | return false | ||
| 135 | } | 131 | } | ||
| 136 | - | if strings.HasPrefix(display, ":") { | 132 | + | if after, ok := strings.CutPrefix(display, ":"); ok { |
| 137 | - | displayNum := strings.TrimPrefix(display, ":") | 133 | + | displayNum := after |
| 138 | displayNum = strings.SplitN(displayNum, ".", 2)[0] | 134 | displayNum = strings.SplitN(displayNum, ".", 2)[0] | ||
| 139 | if displayNum == "" { | 135 | if displayNum == "" { | ||
| 140 | return false | 136 | return false | ||
| @@ -143,12 +139,7 @@ func testHasUsableX11Display() bool { | |||||
| 143 | if fallback := "/tmp/.X11-unix/X" + displayNum; fallback != x11SocketCandidates[0] { | 139 | if fallback := "/tmp/.X11-unix/X" + displayNum; fallback != x11SocketCandidates[0] { | ||
| 144 | x11SocketCandidates = append(x11SocketCandidates, fallback) | 140 | x11SocketCandidates = append(x11SocketCandidates, fallback) | ||
| 145 | } | 141 | } | ||
| 146 | - | for _, candidate := range x11SocketCandidates { | 142 | + | return slices.ContainsFunc(x11SocketCandidates, testPathIsSocket) |
| 147 | - | if testPathIsSocket(candidate) { | |||
| 148 | - | return true | |||
| 149 | - | } | |||
| 150 | - | } | |||
| 151 | - | return false | |||
| 152 | } | 143 | } | ||
| 153 | return true // TCP display | 144 | return true // TCP display | ||
| 154 | } | 145 | } | ||
| @@ -2191,7 +2182,7 @@ func TestApp_HandlePaneFullscreenChanged_ClearsBottomInset(t *testing.T) { | |||||
| 2191 | } | 2182 | } | ||
| 2192 | 2183 | ||||
| 2193 | func TestApp_RemoveBrowserWindowPromotesDeterministicFallbackWithMainWindow(t *testing.T) { | 2184 | func TestApp_RemoveBrowserWindowPromotesDeterministicFallbackWithMainWindow(t *testing.T) { | ||
| 2194 | - | for i := 0; i < 20; i++ { | 2185 | + | for range 20 { |
| 2195 | removed := &browserWindow{id: "window-z", mainWindow: &window.MainWindow{}} | 2186 | removed := &browserWindow{id: "window-z", mainWindow: &window.MainWindow{}} | ||
| 2196 | nilWindow := &browserWindow{id: "window-a"} | 2187 | nilWindow := &browserWindow{id: "window-a"} | ||
| 2197 | firstValid := &browserWindow{id: "window-b", mainWindow: &window.MainWindow{}} | 2188 | firstValid := &browserWindow{id: "window-b", mainWindow: &window.MainWindow{}} | ||
M internal/ui/browser_window.go +4 -5
| @@ -3,6 +3,7 @@ package ui | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "slices" | |
| 6 | 7 | "sort" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -327,10 +328,8 @@ func (a *App) registerBrowserWindow(bw *browserWindow) { | |||
| 327 | 328 | a.browserWindows[bw.id] = bw | |
| 328 | 329 | ||
| 329 | 330 | // Track registration order, guarding against duplicates. | |
| 330 | - | for _, id := range a.browserWindowOrder { | |
| 331 | - | if id == bw.id { | |
| 332 | - | return | |
| 333 | - | } | |
| 331 | + | if slices.Contains(a.browserWindowOrder, bw.id) { | |
| 332 | + | return | |
| 334 | 333 | } | |
| 335 | 334 | a.browserWindowOrder = append(a.browserWindowOrder, bw.id) | |
| 336 | 335 | } | |
| @@ -436,7 +435,7 @@ func sortedTabIDs(tabs map[entity.TabID]*entity.Tab) []entity.TabID { | |||
| 436 | 435 | for tabID := range tabs { | |
| 437 | 436 | ids = append(ids, tabID) | |
| 438 | 437 | } | |
| 439 | - | sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) | |
| 438 | + | slices.Sort(ids) | |
| 440 | 439 | return ids | |
| 441 | 440 | } | |
| 442 | 441 | ||
| @@ -3,6 +3,7 @@ package ui | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "slices" | |||
| 6 | "sort" | 7 | "sort" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -327,10 +328,8 @@ func (a *App) registerBrowserWindow(bw *browserWindow) { | |||||
| 327 | a.browserWindows[bw.id] = bw | 328 | a.browserWindows[bw.id] = bw | ||
| 328 | 329 | ||||
| 329 | // Track registration order, guarding against duplicates. | 330 | // Track registration order, guarding against duplicates. | ||
| 330 | - | for _, id := range a.browserWindowOrder { | 331 | + | if slices.Contains(a.browserWindowOrder, bw.id) { |
| 331 | - | if id == bw.id { | 332 | + | return |
| 332 | - | return | |||
| 333 | - | } | |||
| 334 | } | 333 | } | ||
| 335 | a.browserWindowOrder = append(a.browserWindowOrder, bw.id) | 334 | a.browserWindowOrder = append(a.browserWindowOrder, bw.id) | ||
| 336 | } | 335 | } | ||
| @@ -436,7 +435,7 @@ func sortedTabIDs(tabs map[entity.TabID]*entity.Tab) []entity.TabID { | |||||
| 436 | for tabID := range tabs { | 435 | for tabID := range tabs { | ||
| 437 | ids = append(ids, tabID) | 436 | ids = append(ids, tabID) | ||
| 438 | } | 437 | } | ||
| 439 | - | sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) | 438 | + | slices.Sort(ids) |
| 440 | return ids | 439 | return ids | ||
| 441 | } | 440 | } | ||
| 442 | 441 | ||||
M internal/ui/component/accent_picker.go +2 -2
| @@ -35,7 +35,7 @@ type AccentPicker struct { | |||
| 35 | 35 | cancelCb func() | |
| 36 | 36 | visible bool | |
| 37 | 37 | keyController *gtk.EventControllerKey | |
| 38 | - | retainedClicked []interface{} // Keep click callbacks alive | |
| 38 | + | retainedClicked []any // Keep click callbacks alive | |
| 39 | 39 | ||
| 40 | 40 | mu sync.Mutex | |
| 41 | 41 | } | |
| @@ -100,7 +100,7 @@ func (ap *AccentPicker) Show(accents []rune, selectedCb func(rune), cancelCb fun | |||
| 100 | 100 | ||
| 101 | 101 | // Create new labels for each accent | |
| 102 | 102 | ap.accentLabels = make([]layout.LabelWidget, len(accents)) | |
| 103 | - | ap.retainedClicked = make([]interface{}, len(accents)) | |
| 103 | + | ap.retainedClicked = make([]any, len(accents)) | |
| 104 | 104 | ||
| 105 | 105 | for i, accent := range accents { | |
| 106 | 106 | label := ap.factory.NewLabel(string(accent)) | |
| @@ -35,7 +35,7 @@ type AccentPicker struct { | |||||
| 35 | cancelCb func() | 35 | cancelCb func() | ||
| 36 | visible bool | 36 | visible bool | ||
| 37 | keyController *gtk.EventControllerKey | 37 | keyController *gtk.EventControllerKey | ||
| 38 | - | retainedClicked []interface{} // Keep click callbacks alive | 38 | + | retainedClicked []any // Keep click callbacks alive |
| 39 | 39 | ||||
| 40 | mu sync.Mutex | 40 | mu sync.Mutex | ||
| 41 | } | 41 | } | ||
| @@ -100,7 +100,7 @@ func (ap *AccentPicker) Show(accents []rune, selectedCb func(rune), cancelCb fun | |||||
| 100 | 100 | ||||
| 101 | // Create new labels for each accent | 101 | // Create new labels for each accent | ||
| 102 | ap.accentLabels = make([]layout.LabelWidget, len(accents)) | 102 | ap.accentLabels = make([]layout.LabelWidget, len(accents)) | ||
| 103 | - | ap.retainedClicked = make([]interface{}, len(accents)) | 103 | + | ap.retainedClicked = make([]any, len(accents)) |
| 104 | 104 | ||||
| 105 | for i, accent := range accents { | 105 | for i, accent := range accents { | ||
| 106 | label := ap.factory.NewLabel(string(accent)) | 106 | label := ap.factory.NewLabel(string(accent)) | ||
M internal/ui/component/favorites_sidebar.go +2 -2
| @@ -64,8 +64,8 @@ type FavoritesSidebar struct { | |||
| 64 | 64 | confirmDelete bool | |
| 65 | 65 | confirmDeleteID entity.FavoriteID | |
| 66 | 66 | ||
| 67 | - | retainedCallbacks []interface{} | |
| 68 | - | tagCallbacks []interface{} | |
| 67 | + | retainedCallbacks []any | |
| 68 | + | tagCallbacks []any | |
| 69 | 69 | ctx context.Context | |
| 70 | 70 | cancel context.CancelFunc | |
| 71 | 71 | mu sync.RWMutex | |
| @@ -64,8 +64,8 @@ type FavoritesSidebar struct { | |||||
| 64 | confirmDelete bool | 64 | confirmDelete bool | ||
| 65 | confirmDeleteID entity.FavoriteID | 65 | confirmDeleteID entity.FavoriteID | ||
| 66 | 66 | ||||
| 67 | - | retainedCallbacks []interface{} | 67 | + | retainedCallbacks []any |
| 68 | - | tagCallbacks []interface{} | 68 | + | tagCallbacks []any |
| 69 | ctx context.Context | 69 | ctx context.Context | ||
| 70 | cancel context.CancelFunc | 70 | cancel context.CancelFunc | ||
| 71 | mu sync.RWMutex | 71 | mu sync.RWMutex | ||
M internal/ui/component/favorites_sidebar_rows.go +1 -1
| @@ -89,7 +89,7 @@ func (fs *FavoritesSidebar) renderTags() { | |||
| 89 | 89 | fs.mu.RUnlock() | |
| 90 | 90 | ||
| 91 | 91 | clearBoxChildren(tagBox) | |
| 92 | - | callbacks := make([]interface{}, 0, len(tags)+1) | |
| 92 | + | callbacks := make([]any, 0, len(tags)+1) | |
| 93 | 93 | ||
| 94 | 94 | allButton := gtk.NewButtonWithLabel("All") | |
| 95 | 95 | if allButton != nil { | |
| @@ -89,7 +89,7 @@ func (fs *FavoritesSidebar) renderTags() { | |||||
| 89 | fs.mu.RUnlock() | 89 | fs.mu.RUnlock() | ||
| 90 | 90 | ||||
| 91 | clearBoxChildren(tagBox) | 91 | clearBoxChildren(tagBox) | ||
| 92 | - | callbacks := make([]interface{}, 0, len(tags)+1) | 92 | + | callbacks := make([]any, 0, len(tags)+1) |
| 93 | 93 | ||||
| 94 | allButton := gtk.NewButtonWithLabel("All") | 94 | allButton := gtk.NewButtonWithLabel("All") | ||
| 95 | if allButton != nil { | 95 | if allButton != nil { | ||
M internal/ui/component/find_bar.go +1 -1
| @@ -45,7 +45,7 @@ type FindBar struct { | |||
| 45 | 45 | onClose func() | |
| 46 | 46 | onAccentKeyPress func(keyval uint, state gdk.ModifierType) bool | |
| 47 | 47 | onAccentKeyRelease func(keyval uint) | |
| 48 | - | retainedCallbacks []interface{} // Keep callbacks alive for GC | |
| 48 | + | retainedCallbacks []any // Keep callbacks alive for GC | |
| 49 | 49 | } | |
| 50 | 50 | ||
| 51 | 51 | // FindBarConfig holds configuration for creating a FindBar. | |
| @@ -45,7 +45,7 @@ type FindBar struct { | |||||
| 45 | onClose func() | 45 | onClose func() | ||
| 46 | onAccentKeyPress func(keyval uint, state gdk.ModifierType) bool | 46 | onAccentKeyPress func(keyval uint, state gdk.ModifierType) bool | ||
| 47 | onAccentKeyRelease func(keyval uint) | 47 | onAccentKeyRelease func(keyval uint) | ||
| 48 | - | retainedCallbacks []interface{} // Keep callbacks alive for GC | 48 | + | retainedCallbacks []any // Keep callbacks alive for GC |
| 49 | } | 49 | } | ||
| 50 | 50 | ||||
| 51 | // FindBarConfig holds configuration for creating a FindBar. | 51 | // FindBarConfig holds configuration for creating a FindBar. | ||
M internal/ui/component/floating_pane_test.go +1 -1
| @@ -130,7 +130,7 @@ func TestFloatingPane_ConcurrentResizeAndParentSwitch(t *testing.T) { | |||
| 130 | 130 | }) | |
| 131 | 131 | ||
| 132 | 132 | var wg sync.WaitGroup | |
| 133 | - | for i := 0; i < 200; i++ { | |
| 133 | + | for i := range 200 { | |
| 134 | 134 | wg.Add(2) | |
| 135 | 135 | go func() { | |
| 136 | 136 | defer wg.Done() | |
| @@ -130,7 +130,7 @@ func TestFloatingPane_ConcurrentResizeAndParentSwitch(t *testing.T) { | |||||
| 130 | }) | 130 | }) | ||
| 131 | 131 | ||||
| 132 | var wg sync.WaitGroup | 132 | var wg sync.WaitGroup | ||
| 133 | - | for i := 0; i < 200; i++ { | 133 | + | for i := range 200 { |
| 134 | wg.Add(2) | 134 | wg.Add(2) | ||
| 135 | go func() { | 135 | go func() { | ||
| 136 | defer wg.Done() | 136 | defer wg.Done() | ||
M internal/ui/component/history_model_test.go +1 -1
| @@ -248,7 +248,7 @@ func makeGroups(dayCounts ...int) []historyGroup { | |||
| 248 | 248 | groups := make([]historyGroup, len(dayCounts)) | |
| 249 | 249 | for i, count := range dayCounts { | |
| 250 | 250 | entries := make([]*entity.HistoryEntry, count) | |
| 251 | - | for j := 0; j < count; j++ { | |
| 251 | + | for j := range count { | |
| 252 | 252 | entries[j] = &entity.HistoryEntry{ | |
| 253 | 253 | ID: int64(i*100 + j), | |
| 254 | 254 | URL: fmt.Sprintf("https://entry-%d-%d.com", i, j), | |
| @@ -248,7 +248,7 @@ func makeGroups(dayCounts ...int) []historyGroup { | |||||
| 248 | groups := make([]historyGroup, len(dayCounts)) | 248 | groups := make([]historyGroup, len(dayCounts)) | ||
| 249 | for i, count := range dayCounts { | 249 | for i, count := range dayCounts { | ||
| 250 | entries := make([]*entity.HistoryEntry, count) | 250 | entries := make([]*entity.HistoryEntry, count) | ||
| 251 | - | for j := 0; j < count; j++ { | 251 | + | for j := range count { |
| 252 | entries[j] = &entity.HistoryEntry{ | 252 | entries[j] = &entity.HistoryEntry{ | ||
| 253 | ID: int64(i*100 + j), | 253 | ID: int64(i*100 + j), | ||
| 254 | URL: fmt.Sprintf("https://entry-%d-%d.com", i, j), | 254 | URL: fmt.Sprintf("https://entry-%d-%d.com", i, j), | ||
M internal/ui/component/history_sidebar.go +1 -1
| @@ -99,7 +99,7 @@ type HistorySidebar struct { | |||
| 99 | 99 | relativeTimeDayKeySet bool | |
| 100 | 100 | ||
| 101 | 101 | // Retained callbacks | |
| 102 | - | retainedCallbacks []interface{} | |
| 102 | + | retainedCallbacks []any | |
| 103 | 103 | ||
| 104 | 104 | // idleScheduler dispatches work onto the GTK main thread. | |
| 105 | 105 | // Tests may override it to exercise scheduled callbacks deterministically. | |
| @@ -99,7 +99,7 @@ type HistorySidebar struct { | |||||
| 99 | relativeTimeDayKeySet bool | 99 | relativeTimeDayKeySet bool | ||
| 100 | 100 | ||||
| 101 | // Retained callbacks | 101 | // Retained callbacks | ||
| 102 | - | retainedCallbacks []interface{} | 102 | + | retainedCallbacks []any |
| 103 | 103 | ||||
| 104 | // idleScheduler dispatches work onto the GTK main thread. | 104 | // idleScheduler dispatches work onto the GTK main thread. | ||
| 105 | // Tests may override it to exercise scheduled callbacks deterministically. | 105 | // Tests may override it to exercise scheduled callbacks deterministically. | ||
M internal/ui/component/history_sidebar_keyboard.go +1 -1
| @@ -468,7 +468,7 @@ func (hs *HistorySidebar) ensureRowVisible(index int) { | |||
| 468 | 468 | ||
| 469 | 469 | // Sum allocated heights of all preceding rows to estimate Y position. | |
| 470 | 470 | var yPos int | |
| 471 | - | for i := 0; i < index; i++ { | |
| 471 | + | for i := range index { | |
| 472 | 472 | r := hs.listBox.GetRowAtIndex(i) | |
| 473 | 473 | if r == nil { | |
| 474 | 474 | continue | |
| @@ -468,7 +468,7 @@ func (hs *HistorySidebar) ensureRowVisible(index int) { | |||||
| 468 | 468 | ||||
| 469 | // Sum allocated heights of all preceding rows to estimate Y position. | 469 | // Sum allocated heights of all preceding rows to estimate Y position. | ||
| 470 | var yPos int | 470 | var yPos int | ||
| 471 | - | for i := 0; i < index; i++ { | 471 | + | for i := range index { |
| 472 | r := hs.listBox.GetRowAtIndex(i) | 472 | r := hs.listBox.GetRowAtIndex(i) | ||
| 473 | if r == nil { | 473 | if r == nil { | ||
| 474 | continue | 474 | continue | ||
M internal/ui/component/modal_sizing.go +1 -4
| @@ -165,10 +165,7 @@ func CalculateModalDimensions(parent layout.OverlayWidget, cfg ModalSizeConfig) | |||
| 165 | 165 | if cfg.FixedWidth > 0 { | |
| 166 | 166 | width = cfg.FixedWidth | |
| 167 | 167 | } else { | |
| 168 | - | width = int(float64(parentWidth) * cfg.WidthPct) | |
| 169 | - | if width > cfg.MaxWidth { | |
| 170 | - | width = cfg.MaxWidth | |
| 171 | - | } | |
| 168 | + | width = min(int(float64(parentWidth)*cfg.WidthPct), cfg.MaxWidth) | |
| 172 | 169 | } | |
| 173 | 170 | ||
| 174 | 171 | if cfg.UseFixedTopMargin { | |
| @@ -165,10 +165,7 @@ func CalculateModalDimensions(parent layout.OverlayWidget, cfg ModalSizeConfig) | |||||
| 165 | if cfg.FixedWidth > 0 { | 165 | if cfg.FixedWidth > 0 { | ||
| 166 | width = cfg.FixedWidth | 166 | width = cfg.FixedWidth | ||
| 167 | } else { | 167 | } else { | ||
| 168 | - | width = int(float64(parentWidth) * cfg.WidthPct) | 168 | + | width = min(int(float64(parentWidth)*cfg.WidthPct), cfg.MaxWidth) |
| 169 | - | if width > cfg.MaxWidth { | |||
| 170 | - | width = cfg.MaxWidth | |||
| 171 | - | } | |||
| 172 | } | 169 | } | ||
| 173 | 170 | ||||
| 174 | if cfg.UseFixedTopMargin { | 171 | if cfg.UseFixedTopMargin { | ||
M internal/ui/component/omnibox.go +1 -1
| @@ -159,7 +159,7 @@ type Omnibox struct { | |||
| 159 | 159 | } | |
| 160 | 160 | ||
| 161 | 161 | // Callback retention: keep GTK signal callbacks reachable by Go GC. | |
| 162 | - | retainedCallbacks []interface{} | |
| 162 | + | retainedCallbacks []any | |
| 163 | 163 | ||
| 164 | 164 | // Click outside handler | |
| 165 | 165 | clickOutsideController *gtk.GestureClick | |
| @@ -159,7 +159,7 @@ type Omnibox struct { | |||||
| 159 | } | 159 | } | ||
| 160 | 160 | ||||
| 161 | // Callback retention: keep GTK signal callbacks reachable by Go GC. | 161 | // Callback retention: keep GTK signal callbacks reachable by Go GC. | ||
| 162 | - | retainedCallbacks []interface{} | 162 | + | retainedCallbacks []any |
| 163 | 163 | ||||
| 164 | // Click outside handler | 164 | // Click outside handler | ||
| 165 | clickOutsideController *gtk.GestureClick | 165 | clickOutsideController *gtk.GestureClick | ||
M internal/ui/component/permission_popup.go +1 -1
| @@ -35,7 +35,7 @@ type PermissionPopup struct { | |||
| 35 | 35 | visible bool | |
| 36 | 36 | callback func(allowed, persistent bool) | |
| 37 | 37 | ||
| 38 | - | retainedCallbacks []interface{} | |
| 38 | + | retainedCallbacks []any | |
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | 41 | // NewPermissionPopup creates a new permission popup component. | |
| @@ -35,7 +35,7 @@ type PermissionPopup struct { | |||||
| 35 | visible bool | 35 | visible bool | ||
| 36 | callback func(allowed, persistent bool) | 36 | callback func(allowed, persistent bool) | ||
| 37 | 37 | ||||
| 38 | - | retainedCallbacks []interface{} | 38 | + | retainedCallbacks []any |
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | // NewPermissionPopup creates a new permission popup component. | 41 | // NewPermissionPopup creates a new permission popup component. | ||
M internal/ui/component/session_manager.go +6 -5
| @@ -58,7 +58,7 @@ type SessionManager struct { | |||
| 58 | 58 | onToast func(ctx context.Context, message string, level ToastLevel) | |
| 59 | 59 | ||
| 60 | 60 | // GTK callback retention | |
| 61 | - | retainedCallbacks []interface{} | |
| 61 | + | retainedCallbacks []any | |
| 62 | 62 | ||
| 63 | 63 | // Scaling | |
| 64 | 64 | uiScale float64 | |
| @@ -712,9 +712,10 @@ func (sm *SessionManager) createTreeRow(text, nodeType string, hasSibling bool, | |||
| 712 | 712 | ||
| 713 | 713 | // Build indent and branch prefix | |
| 714 | 714 | var prefix string | |
| 715 | - | indent := " " // Base indent (minimal, tree branches provide visual alignment) | |
| 716 | - | for i := 0; i < depth; i++ { | |
| 717 | - | indent += " " // 3 spaces per depth level | |
| 715 | + | var indent strings.Builder | |
| 716 | + | indent.WriteString(" ") // Base indent (minimal, tree branches provide visual alignment) | |
| 717 | + | for range depth { | |
| 718 | + | indent.WriteString(" ") // 3 spaces per depth level | |
| 718 | 719 | } | |
| 719 | 720 | ||
| 720 | 721 | // Branch character | |
| @@ -738,7 +739,7 @@ func (sm *SessionManager) createTreeRow(text, nodeType string, hasSibling bool, | |||
| 738 | 739 | } | |
| 739 | 740 | ||
| 740 | 741 | // Create the label with all parts | |
| 741 | - | labelText := indent + prefix + icon + text | |
| 742 | + | labelText := indent.String() + prefix + icon + text | |
| 742 | 743 | label := gtk.NewLabel(&labelText) | |
| 743 | 744 | if label == nil { | |
| 744 | 745 | return nil | |
| @@ -58,7 +58,7 @@ type SessionManager struct { | |||||
| 58 | onToast func(ctx context.Context, message string, level ToastLevel) | 58 | onToast func(ctx context.Context, message string, level ToastLevel) | ||
| 59 | 59 | ||||
| 60 | // GTK callback retention | 60 | // GTK callback retention | ||
| 61 | - | retainedCallbacks []interface{} | 61 | + | retainedCallbacks []any |
| 62 | 62 | ||||
| 63 | // Scaling | 63 | // Scaling | ||
| 64 | uiScale float64 | 64 | uiScale float64 | ||
| @@ -712,9 +712,10 @@ func (sm *SessionManager) createTreeRow(text, nodeType string, hasSibling bool, | |||||
| 712 | 712 | ||||
| 713 | // Build indent and branch prefix | 713 | // Build indent and branch prefix | ||
| 714 | var prefix string | 714 | var prefix string | ||
| 715 | - | indent := " " // Base indent (minimal, tree branches provide visual alignment) | 715 | + | var indent strings.Builder |
| 716 | - | for i := 0; i < depth; i++ { | 716 | + | indent.WriteString(" ") // Base indent (minimal, tree branches provide visual alignment) |
| 717 | - | indent += " " // 3 spaces per depth level | 717 | + | for range depth { |
| 718 | + | indent.WriteString(" ") // 3 spaces per depth level | |||
| 718 | } | 719 | } | ||
| 719 | 720 | ||||
| 720 | // Branch character | 721 | // Branch character | ||
| @@ -738,7 +739,7 @@ func (sm *SessionManager) createTreeRow(text, nodeType string, hasSibling bool, | |||||
| 738 | } | 739 | } | ||
| 739 | 740 | ||||
| 740 | // Create the label with all parts | 741 | // Create the label with all parts | ||
| 741 | - | labelText := indent + prefix + icon + text | 742 | + | labelText := indent.String() + prefix + icon + text |
| 742 | label := gtk.NewLabel(&labelText) | 743 | label := gtk.NewLabel(&labelText) | ||
| 743 | if label == nil { | 744 | if label == nil { | ||
| 744 | return nil | 745 | return nil | ||
M internal/ui/component/tab_picker.go +2 -8
| @@ -42,7 +42,7 @@ type TabPicker struct { | |||
| 42 | 42 | onClose func() | |
| 43 | 43 | onSelect func(item TabPickerItem) | |
| 44 | 44 | ||
| 45 | - | retainedCallbacks []interface{} | |
| 45 | + | retainedCallbacks []any | |
| 46 | 46 | ctx context.Context | |
| 47 | 47 | } | |
| 48 | 48 | ||
| @@ -293,13 +293,7 @@ func (tp *TabPicker) resizeAndCenter() { | |||
| 293 | 293 | if tp.scrolledWindow != nil { | |
| 294 | 294 | rowH := ScaleValue(DefaultRowHeights.Standard, tp.uiScale) | |
| 295 | 295 | maxH := TabPickerListDefaults.MaxVisibleRows * rowH | |
| 296 | - | h := count * rowH | |
| 297 | - | if h > maxH { | |
| 298 | - | h = maxH | |
| 299 | - | } | |
| 300 | - | if h < rowH { | |
| 301 | - | h = rowH | |
| 302 | - | } | |
| 296 | + | h := max(min(count*rowH, maxH), rowH) | |
| 303 | 297 | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 304 | 298 | SetScrolledWindowHeight(tp.scrolledWindow, h) | |
| 305 | 299 | tp.outerBox.QueueResize() | |
| @@ -42,7 +42,7 @@ type TabPicker struct { | |||||
| 42 | onClose func() | 42 | onClose func() | ||
| 43 | onSelect func(item TabPickerItem) | 43 | onSelect func(item TabPickerItem) | ||
| 44 | 44 | ||||
| 45 | - | retainedCallbacks []interface{} | 45 | + | retainedCallbacks []any |
| 46 | ctx context.Context | 46 | ctx context.Context | ||
| 47 | } | 47 | } | ||
| 48 | 48 | ||||
| @@ -293,13 +293,7 @@ func (tp *TabPicker) resizeAndCenter() { | |||||
| 293 | if tp.scrolledWindow != nil { | 293 | if tp.scrolledWindow != nil { | ||
| 294 | rowH := ScaleValue(DefaultRowHeights.Standard, tp.uiScale) | 294 | rowH := ScaleValue(DefaultRowHeights.Standard, tp.uiScale) | ||
| 295 | maxH := TabPickerListDefaults.MaxVisibleRows * rowH | 295 | maxH := TabPickerListDefaults.MaxVisibleRows * rowH | ||
| 296 | - | h := count * rowH | 296 | + | h := max(min(count*rowH, maxH), rowH) |
| 297 | - | if h > maxH { | |||
| 298 | - | h = maxH | |||
| 299 | - | } | |||
| 300 | - | if h < rowH { | |||
| 301 | - | h = rowH | |||
| 302 | - | } | |||
| 303 | cb := glib.SourceFunc(func(_ uintptr) bool { | 297 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| 304 | SetScrolledWindowHeight(tp.scrolledWindow, h) | 298 | SetScrolledWindowHeight(tp.scrolledWindow, h) | ||
| 305 | tp.outerBox.QueueResize() | 299 | tp.outerBox.QueueResize() | ||
M internal/ui/component/webrtc_permission_indicator.go +1 -1
| @@ -41,7 +41,7 @@ type WebRTCPermissionIndicator struct { | |||
| 41 | 41 | ||
| 42 | 42 | mu sync.Mutex | |
| 43 | 43 | expanded bool | |
| 44 | - | retainedCallbacks []interface{} | |
| 44 | + | retainedCallbacks []any | |
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | 47 | // NewWebRTCPermissionIndicator creates a top-right WebRTC activity indicator. | |
| @@ -41,7 +41,7 @@ type WebRTCPermissionIndicator struct { | |||||
| 41 | 41 | ||||
| 42 | mu sync.Mutex | 42 | mu sync.Mutex | ||
| 43 | expanded bool | 43 | expanded bool | ||
| 44 | - | retainedCallbacks []interface{} | 44 | + | retainedCallbacks []any |
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | // NewWebRTCPermissionIndicator creates a top-right WebRTC activity indicator. | 47 | // NewWebRTCPermissionIndicator creates a top-right WebRTC activity indicator. | ||
M internal/ui/coordinator/content/appearance.go +2 -3
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "encoding/json" | |
| 6 | 6 | "fmt" | |
| 7 | + | "maps" | |
| 7 | 8 | "strings" | |
| 8 | 9 | "time" | |
| 9 | 10 | ||
| @@ -44,9 +45,7 @@ func (c *Coordinator) ApplyWebUIThemeToAll(ctx context.Context, prefersDark bool | |||
| 44 | 45 | // Snapshot webViews under lock to avoid data race with concurrent popup create/close. | |
| 45 | 46 | c.webViewsMu.RLock() | |
| 46 | 47 | snapshot := make(map[entity.PaneID]port.WebView, len(c.webViews)) | |
| 47 | - | for k, v := range c.webViews { | |
| 48 | - | snapshot[k] = v | |
| 49 | - | } | |
| 48 | + | maps.Copy(snapshot, c.webViews) | |
| 50 | 49 | c.webViewsMu.RUnlock() | |
| 51 | 50 | ||
| 52 | 51 | for paneID, wv := range snapshot { | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | + | "maps" | |||
| 7 | "strings" | 8 | "strings" | ||
| 8 | "time" | 9 | "time" | ||
| 9 | 10 | ||||
| @@ -44,9 +45,7 @@ func (c *Coordinator) ApplyWebUIThemeToAll(ctx context.Context, prefersDark bool | |||||
| 44 | // Snapshot webViews under lock to avoid data race with concurrent popup create/close. | 45 | // Snapshot webViews under lock to avoid data race with concurrent popup create/close. | ||
| 45 | c.webViewsMu.RLock() | 46 | c.webViewsMu.RLock() | ||
| 46 | snapshot := make(map[entity.PaneID]port.WebView, len(c.webViews)) | 47 | snapshot := make(map[entity.PaneID]port.WebView, len(c.webViews)) | ||
| 47 | - | for k, v := range c.webViews { | 48 | + | maps.Copy(snapshot, c.webViews) |
| 48 | - | snapshot[k] = v | |||
| 49 | - | } | |||
| 50 | c.webViewsMu.RUnlock() | 49 | c.webViewsMu.RUnlock() | ||
| 51 | 50 | ||||
| 52 | for paneID, wv := range snapshot { | 51 | for paneID, wv := range snapshot { | ||
M internal/ui/coordinator/content/bulk.go +2 -3
| @@ -2,6 +2,7 @@ package content | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "maps" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 7 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| @@ -63,9 +64,7 @@ func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) { | |||
| 63 | 64 | // Snapshot webViews under lock to avoid data race with concurrent popup create/close. | |
| 64 | 65 | c.webViewsMu.RLock() | |
| 65 | 66 | snapshot := make(map[entity.PaneID]port.WebView, len(c.webViews)) | |
| 66 | - | for k, v := range c.webViews { | |
| 67 | - | snapshot[k] = v | |
| 68 | - | } | |
| 67 | + | maps.Copy(snapshot, c.webViews) | |
| 69 | 68 | c.webViewsMu.RUnlock() | |
| 70 | 69 | ||
| 71 | 70 | for paneID, wv := range snapshot { | |
| @@ -2,6 +2,7 @@ package content | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | + | "maps" | |||
| 5 | 6 | ||||
| 6 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 7 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| @@ -63,9 +64,7 @@ func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) { | |||||
| 63 | // Snapshot webViews under lock to avoid data race with concurrent popup create/close. | 64 | // Snapshot webViews under lock to avoid data race with concurrent popup create/close. | ||
| 64 | c.webViewsMu.RLock() | 65 | c.webViewsMu.RLock() | ||
| 65 | snapshot := make(map[entity.PaneID]port.WebView, len(c.webViews)) | 66 | snapshot := make(map[entity.PaneID]port.WebView, len(c.webViews)) | ||
| 66 | - | for k, v := range c.webViews { | 67 | + | maps.Copy(snapshot, c.webViews) |
| 67 | - | snapshot[k] = v | |||
| 68 | - | } | |||
| 69 | c.webViewsMu.RUnlock() | 68 | c.webViewsMu.RUnlock() | ||
| 70 | 69 | ||||
| 71 | for paneID, wv := range snapshot { | 70 | for paneID, wv := range snapshot { | ||
M internal/ui/coordinator/content/content_reveal_test.go +1 -1
| @@ -152,7 +152,7 @@ func TestPendingReveal_ConcurrentAccess(t *testing.T) { | |||
| 152 | 152 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3", "pane-4", "pane-5"} | |
| 153 | 153 | ||
| 154 | 154 | // Concurrently mark and clear panes | |
| 155 | - | for i := 0; i < 100; i++ { | |
| 155 | + | for range 100 { | |
| 156 | 156 | for _, paneID := range paneIDs { | |
| 157 | 157 | wg.Add(3) | |
| 158 | 158 | ||
| @@ -152,7 +152,7 @@ func TestPendingReveal_ConcurrentAccess(t *testing.T) { | |||||
| 152 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3", "pane-4", "pane-5"} | 152 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3", "pane-4", "pane-5"} | ||
| 153 | 153 | ||||
| 154 | // Concurrently mark and clear panes | 154 | // Concurrently mark and clear panes | ||
| 155 | - | for i := 0; i < 100; i++ { | 155 | + | for range 100 { |
| 156 | for _, paneID := range paneIDs { | 156 | for _, paneID := range paneIDs { | ||
| 157 | wg.Add(3) | 157 | wg.Add(3) | ||
| 158 | 158 | ||||
M internal/ui/coordinator/content/popup_test.go +5 -5
| @@ -806,7 +806,7 @@ func TestPendingPopups_ConcurrentAccess(t *testing.T) { | |||
| 806 | 806 | wg.Add(workers * 2) | |
| 807 | 807 | ||
| 808 | 808 | // Writers | |
| 809 | - | for i := 0; i < workers; i++ { | |
| 809 | + | for i := range workers { | |
| 810 | 810 | go func(id int) { | |
| 811 | 811 | defer wg.Done() | |
| 812 | 812 | popupID := port.WebViewID(id) | |
| @@ -820,7 +820,7 @@ func TestPendingPopups_ConcurrentAccess(t *testing.T) { | |||
| 820 | 820 | } | |
| 821 | 821 | ||
| 822 | 822 | // Readers | |
| 823 | - | for i := 0; i < workers; i++ { | |
| 823 | + | for i := range workers { | |
| 824 | 824 | go func(id int) { | |
| 825 | 825 | defer wg.Done() | |
| 826 | 826 | popupID := port.WebViewID(id) | |
| @@ -847,7 +847,7 @@ func TestPendingPopups_ConcurrentDeleteAndRead(t *testing.T) { | |||
| 847 | 847 | } | |
| 848 | 848 | ||
| 849 | 849 | // Pre-populate | |
| 850 | - | for i := 0; i < 10; i++ { | |
| 850 | + | for i := range 10 { | |
| 851 | 851 | c.popups.pendingPopups[port.WebViewID(i)] = &PendingPopup{PopupType: PopupTypePopup} | |
| 852 | 852 | } | |
| 853 | 853 | ||
| @@ -855,7 +855,7 @@ func TestPendingPopups_ConcurrentDeleteAndRead(t *testing.T) { | |||
| 855 | 855 | wg.Add(20) | |
| 856 | 856 | ||
| 857 | 857 | // Deleters | |
| 858 | - | for i := 0; i < 10; i++ { | |
| 858 | + | for i := range 10 { | |
| 859 | 859 | go func(id int) { | |
| 860 | 860 | defer wg.Done() | |
| 861 | 861 | c.popups.mu.Lock() | |
| @@ -865,7 +865,7 @@ func TestPendingPopups_ConcurrentDeleteAndRead(t *testing.T) { | |||
| 865 | 865 | } | |
| 866 | 866 | ||
| 867 | 867 | // Readers | |
| 868 | - | for i := 0; i < 10; i++ { | |
| 868 | + | for i := range 10 { | |
| 869 | 869 | go func(id int) { | |
| 870 | 870 | defer wg.Done() | |
| 871 | 871 | c.popups.mu.RLock() | |
| @@ -806,7 +806,7 @@ func TestPendingPopups_ConcurrentAccess(t *testing.T) { | |||||
| 806 | wg.Add(workers * 2) | 806 | wg.Add(workers * 2) | ||
| 807 | 807 | ||||
| 808 | // Writers | 808 | // Writers | ||
| 809 | - | for i := 0; i < workers; i++ { | 809 | + | for i := range workers { |
| 810 | go func(id int) { | 810 | go func(id int) { | ||
| 811 | defer wg.Done() | 811 | defer wg.Done() | ||
| 812 | popupID := port.WebViewID(id) | 812 | popupID := port.WebViewID(id) | ||
| @@ -820,7 +820,7 @@ func TestPendingPopups_ConcurrentAccess(t *testing.T) { | |||||
| 820 | } | 820 | } | ||
| 821 | 821 | ||||
| 822 | // Readers | 822 | // Readers | ||
| 823 | - | for i := 0; i < workers; i++ { | 823 | + | for i := range workers { |
| 824 | go func(id int) { | 824 | go func(id int) { | ||
| 825 | defer wg.Done() | 825 | defer wg.Done() | ||
| 826 | popupID := port.WebViewID(id) | 826 | popupID := port.WebViewID(id) | ||
| @@ -847,7 +847,7 @@ func TestPendingPopups_ConcurrentDeleteAndRead(t *testing.T) { | |||||
| 847 | } | 847 | } | ||
| 848 | 848 | ||||
| 849 | // Pre-populate | 849 | // Pre-populate | ||
| 850 | - | for i := 0; i < 10; i++ { | 850 | + | for i := range 10 { |
| 851 | c.popups.pendingPopups[port.WebViewID(i)] = &PendingPopup{PopupType: PopupTypePopup} | 851 | c.popups.pendingPopups[port.WebViewID(i)] = &PendingPopup{PopupType: PopupTypePopup} | ||
| 852 | } | 852 | } | ||
| 853 | 853 | ||||
| @@ -855,7 +855,7 @@ func TestPendingPopups_ConcurrentDeleteAndRead(t *testing.T) { | |||||
| 855 | wg.Add(20) | 855 | wg.Add(20) | ||
| 856 | 856 | ||||
| 857 | // Deleters | 857 | // Deleters | ||
| 858 | - | for i := 0; i < 10; i++ { | 858 | + | for i := range 10 { |
| 859 | go func(id int) { | 859 | go func(id int) { | ||
| 860 | defer wg.Done() | 860 | defer wg.Done() | ||
| 861 | c.popups.mu.Lock() | 861 | c.popups.mu.Lock() | ||
| @@ -865,7 +865,7 @@ func TestPendingPopups_ConcurrentDeleteAndRead(t *testing.T) { | |||||
| 865 | } | 865 | } | ||
| 866 | 866 | ||||
| 867 | // Readers | 867 | // Readers | ||
| 868 | - | for i := 0; i < 10; i++ { | 868 | + | for i := range 10 { |
| 869 | go func(id int) { | 869 | go func(id int) { | ||
| 870 | defer wg.Done() | 870 | defer wg.Done() | ||
| 871 | c.popups.mu.RLock() | 871 | c.popups.mu.RLock() | ||
M internal/ui/coordinator/content/title_test.go +2 -2
| @@ -137,7 +137,7 @@ func TestPaneTitles_ConcurrentAccess(t *testing.T) { | |||
| 137 | 137 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3"} | |
| 138 | 138 | ||
| 139 | 139 | var wg sync.WaitGroup | |
| 140 | - | for i := 0; i < 50; i++ { | |
| 140 | + | for range 50 { | |
| 141 | 141 | for _, paneID := range paneIDs { | |
| 142 | 142 | wg.Add(2) | |
| 143 | 143 | ||
| @@ -265,7 +265,7 @@ func TestNavOrigins_ConcurrentAccess(t *testing.T) { | |||
| 265 | 265 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3"} | |
| 266 | 266 | ||
| 267 | 267 | var wg sync.WaitGroup | |
| 268 | - | for i := 0; i < 50; i++ { | |
| 268 | + | for range 50 { | |
| 269 | 269 | for _, paneID := range paneIDs { | |
| 270 | 270 | wg.Add(2) | |
| 271 | 271 | ||
| @@ -137,7 +137,7 @@ func TestPaneTitles_ConcurrentAccess(t *testing.T) { | |||||
| 137 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3"} | 137 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3"} | ||
| 138 | 138 | ||||
| 139 | var wg sync.WaitGroup | 139 | var wg sync.WaitGroup | ||
| 140 | - | for i := 0; i < 50; i++ { | 140 | + | for range 50 { |
| 141 | for _, paneID := range paneIDs { | 141 | for _, paneID := range paneIDs { | ||
| 142 | wg.Add(2) | 142 | wg.Add(2) | ||
| 143 | 143 | ||||
| @@ -265,7 +265,7 @@ func TestNavOrigins_ConcurrentAccess(t *testing.T) { | |||||
| 265 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3"} | 265 | paneIDs := []entity.PaneID{"pane-1", "pane-2", "pane-3"} | ||
| 266 | 266 | ||||
| 267 | var wg sync.WaitGroup | 267 | var wg sync.WaitGroup | ||
| 268 | - | for i := 0; i < 50; i++ { | 268 | + | for range 50 { |
| 269 | for _, paneID := range paneIDs { | 269 | for _, paneID := range paneIDs { | ||
| 270 | wg.Add(2) | 270 | wg.Add(2) | ||
| 271 | 271 | ||||
M internal/ui/coordinator/workspace_close_test.go +1 -1
| @@ -171,7 +171,7 @@ func TestDeriveIncrementalCloseTreeContext_ConcurrentPaneAndTabCloseSnapshots(t | |||
| 171 | 171 | ||
| 172 | 172 | run := func(node *entity.PaneNode, wg *sync.WaitGroup, errCh chan<- error) { | |
| 173 | 173 | defer wg.Done() | |
| 174 | - | for i := 0; i < 200; i++ { | |
| 174 | + | for range 200 { | |
| 175 | 175 | if _, err := deriveIncrementalCloseTreeContext(node); err != nil { | |
| 176 | 176 | errCh <- err | |
| 177 | 177 | return | |
| @@ -171,7 +171,7 @@ func TestDeriveIncrementalCloseTreeContext_ConcurrentPaneAndTabCloseSnapshots(t | |||||
| 171 | 171 | ||||
| 172 | run := func(node *entity.PaneNode, wg *sync.WaitGroup, errCh chan<- error) { | 172 | run := func(node *entity.PaneNode, wg *sync.WaitGroup, errCh chan<- error) { | ||
| 173 | defer wg.Done() | 173 | defer wg.Done() | ||
| 174 | - | for i := 0; i < 200; i++ { | 174 | + | for range 200 { |
| 175 | if _, err := deriveIncrementalCloseTreeContext(node); err != nil { | 175 | if _, err := deriveIncrementalCloseTreeContext(node); err != nil { | ||
| 176 | errCh <- err | 176 | errCh <- err | ||
| 177 | return | 177 | return | ||
M internal/ui/floating_pane_test.go +1 -1
| @@ -277,7 +277,7 @@ func TestFloatingPane_ProfileRapidToggleNeverShowsAboutBlank(t *testing.T) { | |||
| 277 | 277 | baseURL := "https://mail.google.com" | |
| 278 | 278 | ||
| 279 | 279 | // Rapid toggle 10 times: should never end up on about:blank | |
| 280 | - | for i := 0; i < 10; i++ { | |
| 280 | + | for i := range 10 { | |
| 281 | 281 | require.NoError(t, app.OpenFloatingPaneProfileURL(context.Background(), "profile:gmail", baseURL)) | |
| 282 | 282 | assert.NotEqual(t, "about:blank", session.pane.CurrentURL(), | |
| 283 | 283 | "iteration %d: profile session should never show about:blank", i) | |
| @@ -277,7 +277,7 @@ func TestFloatingPane_ProfileRapidToggleNeverShowsAboutBlank(t *testing.T) { | |||||
| 277 | baseURL := "https://mail.google.com" | 277 | baseURL := "https://mail.google.com" | ||
| 278 | 278 | ||||
| 279 | // Rapid toggle 10 times: should never end up on about:blank | 279 | // Rapid toggle 10 times: should never end up on about:blank | ||
| 280 | - | for i := 0; i < 10; i++ { | 280 | + | for i := range 10 { |
| 281 | require.NoError(t, app.OpenFloatingPaneProfileURL(context.Background(), "profile:gmail", baseURL)) | 281 | require.NoError(t, app.OpenFloatingPaneProfileURL(context.Background(), "profile:gmail", baseURL)) | ||
| 282 | assert.NotEqual(t, "about:blank", session.pane.CurrentURL(), | 282 | assert.NotEqual(t, "about:blank", session.pane.CurrentURL(), | ||
| 283 | "iteration %d: profile session should never show about:blank", i) | 283 | "iteration %d: profile session should never show about:blank", i) | ||
M internal/ui/input/global_shortcuts.go +3 -6
| @@ -4,6 +4,7 @@ package input | |||
| 4 | 4 | import ( | |
| 5 | 5 | "context" | |
| 6 | 6 | "fmt" | |
| 7 | + | "maps" | |
| 7 | 8 | "strings" | |
| 8 | 9 | "time" | |
| 9 | 10 | ||
| @@ -99,9 +100,7 @@ func NewGlobalShortcutHandler( | |||
| 99 | 100 | } | |
| 100 | 101 | ||
| 101 | 102 | occupied := make(map[KeyBinding]Action, len(h.registered)) | |
| 102 | - | for binding, action := range h.registered { | |
| 103 | - | occupied[binding] = action | |
| 104 | - | } | |
| 103 | + | maps.Copy(occupied, h.registered) | |
| 105 | 104 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | |
| 106 | 105 | if !h.registerShortcut(shortcut.Binding.Keyval, gdk.ModifierType(shortcut.Binding.Modifiers), shortcut.Action) { | |
| 107 | 106 | continue | |
| @@ -553,9 +552,7 @@ func (h *GlobalShortcutHandler) ReloadShortcuts(ctx context.Context, workspace * | |||
| 553 | 552 | } | |
| 554 | 553 | ||
| 555 | 554 | occupied := make(map[KeyBinding]Action, len(h.registered)) | |
| 556 | - | for binding, action := range h.registered { | |
| 557 | - | occupied[binding] = action | |
| 558 | - | } | |
| 555 | + | maps.Copy(occupied, h.registered) | |
| 559 | 556 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | |
| 560 | 557 | if h.registerShortcut(shortcut.Binding.Keyval, gdk.ModifierType(shortcut.Binding.Modifiers), shortcut.Action) { | |
| 561 | 558 | if url, ok := ParseFloatingProfileAction(shortcut.Action); ok { | |
| @@ -4,6 +4,7 @@ package input | |||||
| 4 | import ( | 4 | import ( | ||
| 5 | "context" | 5 | "context" | ||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | + | "maps" | |||
| 7 | "strings" | 8 | "strings" | ||
| 8 | "time" | 9 | "time" | ||
| 9 | 10 | ||||
| @@ -99,9 +100,7 @@ func NewGlobalShortcutHandler( | |||||
| 99 | } | 100 | } | ||
| 100 | 101 | ||||
| 101 | occupied := make(map[KeyBinding]Action, len(h.registered)) | 102 | occupied := make(map[KeyBinding]Action, len(h.registered)) | ||
| 102 | - | for binding, action := range h.registered { | 103 | + | maps.Copy(occupied, h.registered) |
| 103 | - | occupied[binding] = action | |||
| 104 | - | } | |||
| 105 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | 104 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | ||
| 106 | if !h.registerShortcut(shortcut.Binding.Keyval, gdk.ModifierType(shortcut.Binding.Modifiers), shortcut.Action) { | 105 | if !h.registerShortcut(shortcut.Binding.Keyval, gdk.ModifierType(shortcut.Binding.Modifiers), shortcut.Action) { | ||
| 107 | continue | 106 | continue | ||
| @@ -553,9 +552,7 @@ func (h *GlobalShortcutHandler) ReloadShortcuts(ctx context.Context, workspace * | |||||
| 553 | } | 552 | } | ||
| 554 | 553 | ||||
| 555 | occupied := make(map[KeyBinding]Action, len(h.registered)) | 554 | occupied := make(map[KeyBinding]Action, len(h.registered)) | ||
| 556 | - | for binding, action := range h.registered { | 555 | + | maps.Copy(occupied, h.registered) |
| 557 | - | occupied[binding] = action | |||
| 558 | - | } | |||
| 559 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | 556 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | ||
| 560 | if h.registerShortcut(shortcut.Binding.Keyval, gdk.ModifierType(shortcut.Binding.Modifiers), shortcut.Action) { | 557 | if h.registerShortcut(shortcut.Binding.Keyval, gdk.ModifierType(shortcut.Binding.Modifiers), shortcut.Action) { | ||
| 561 | if url, ok := ParseFloatingProfileAction(shortcut.Action); ok { | 558 | if url, ok := ParseFloatingProfileAction(shortcut.Action); ok { | ||
M internal/ui/input/global_shortcuts_test.go +1 -1
| @@ -15,7 +15,7 @@ func TestGlobalShortcutHandlerCallbackBudgetIsIndependentOfShortcutCount(t *test | |||
| 15 | 15 | shortcutRefs: make(map[string]globalShortcutRegistration), | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | - | for i := 0; i < 57; i++ { | |
| 18 | + | for i := range 57 { | |
| 19 | 19 | binding := KeyBinding{Keyval: uint(gdk.KEY_1) + uint(i), Modifiers: Modifier(gdk.AltMaskValue)} | |
| 20 | 20 | action := ActionSwitchTabIndex1 | |
| 21 | 21 | shortcutID := encodeGlobalShortcutID(action, binding, h.generation) | |
| @@ -15,7 +15,7 @@ func TestGlobalShortcutHandlerCallbackBudgetIsIndependentOfShortcutCount(t *test | |||||
| 15 | shortcutRefs: make(map[string]globalShortcutRegistration), | 15 | shortcutRefs: make(map[string]globalShortcutRegistration), | ||
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | - | for i := 0; i < 57; i++ { | 18 | + | for i := range 57 { |
| 19 | binding := KeyBinding{Keyval: uint(gdk.KEY_1) + uint(i), Modifiers: Modifier(gdk.AltMaskValue)} | 19 | binding := KeyBinding{Keyval: uint(gdk.KEY_1) + uint(i), Modifiers: Modifier(gdk.AltMaskValue)} | ||
| 20 | action := ActionSwitchTabIndex1 | 20 | action := ActionSwitchTabIndex1 | ||
| 21 | shortcutID := encodeGlobalShortcutID(action, binding, h.generation) | 21 | shortcutID := encodeGlobalShortcutID(action, binding, h.generation) | ||
M internal/ui/input/modal_test.go +4 -6
| @@ -237,11 +237,9 @@ func TestModalState_ConcurrentAccess(t *testing.T) { | |||
| 237 | 237 | iterations := 100 | |
| 238 | 238 | ||
| 239 | 239 | // Multiple goroutines entering/exiting modes | |
| 240 | - | for i := 0; i < 10; i++ { | |
| 241 | - | wg.Add(1) | |
| 242 | - | go func() { | |
| 243 | - | defer wg.Done() | |
| 244 | - | for j := 0; j < iterations; j++ { | |
| 240 | + | for range 10 { | |
| 241 | + | wg.Go(func() { | |
| 242 | + | for range iterations { | |
| 245 | 243 | ms.EnterTabMode(ctx, 0) | |
| 246 | 244 | _ = ms.Mode() | |
| 247 | 245 | ms.ExitMode(ctx) | |
| @@ -249,7 +247,7 @@ func TestModalState_ConcurrentAccess(t *testing.T) { | |||
| 249 | 247 | _ = ms.Mode() | |
| 250 | 248 | ms.ExitMode(ctx) | |
| 251 | 249 | } | |
| 252 | - | }() | |
| 250 | + | }) | |
| 253 | 251 | } | |
| 254 | 252 | ||
| 255 | 253 | wg.Wait() | |
| @@ -237,11 +237,9 @@ func TestModalState_ConcurrentAccess(t *testing.T) { | |||||
| 237 | iterations := 100 | 237 | iterations := 100 | ||
| 238 | 238 | ||||
| 239 | // Multiple goroutines entering/exiting modes | 239 | // Multiple goroutines entering/exiting modes | ||
| 240 | - | for i := 0; i < 10; i++ { | 240 | + | for range 10 { |
| 241 | - | wg.Add(1) | 241 | + | wg.Go(func() { |
| 242 | - | go func() { | 242 | + | for range iterations { |
| 243 | - | defer wg.Done() | |||
| 244 | - | for j := 0; j < iterations; j++ { | |||
| 245 | ms.EnterTabMode(ctx, 0) | 243 | ms.EnterTabMode(ctx, 0) | ||
| 246 | _ = ms.Mode() | 244 | _ = ms.Mode() | ||
| 247 | ms.ExitMode(ctx) | 245 | ms.ExitMode(ctx) | ||
| @@ -249,7 +247,7 @@ func TestModalState_ConcurrentAccess(t *testing.T) { | |||||
| 249 | _ = ms.Mode() | 247 | _ = ms.Mode() | ||
| 250 | ms.ExitMode(ctx) | 248 | ms.ExitMode(ctx) | ||
| 251 | } | 249 | } | ||
| 252 | - | }() | 250 | + | }) |
| 253 | } | 251 | } | ||
| 254 | 252 | ||||
| 255 | wg.Wait() | 253 | wg.Wait() | ||
M internal/ui/input/shortcuts.go +2 -3
| @@ -3,6 +3,7 @@ package input | |||
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | 5 | "context" | |
| 6 | + | "maps" | |
| 6 | 7 | "sort" | |
| 7 | 8 | "strings" | |
| 8 | 9 | ||
| @@ -380,9 +381,7 @@ func (s *ShortcutSet) registerConfiguredShortcuts(cfg *entity.WorkspaceConfig) { | |||
| 380 | 381 | ||
| 381 | 382 | func (s *ShortcutSet) registerFloatingProfileShortcutsFromWorkspace(ctx context.Context, workspace *entity.WorkspaceConfig) { | |
| 382 | 383 | occupied := make(map[KeyBinding]Action, len(s.Global)) | |
| 383 | - | for binding, action := range s.Global { | |
| 384 | - | occupied[binding] = action | |
| 385 | - | } | |
| 384 | + | maps.Copy(occupied, s.Global) | |
| 386 | 385 | ||
| 387 | 386 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | |
| 388 | 387 | s.Global[shortcut.Binding] = shortcut.Action | |
| @@ -3,6 +3,7 @@ package input | |||||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | "context" | 5 | "context" | ||
| 6 | + | "maps" | |||
| 6 | "sort" | 7 | "sort" | ||
| 7 | "strings" | 8 | "strings" | ||
| 8 | 9 | ||||
| @@ -380,9 +381,7 @@ func (s *ShortcutSet) registerConfiguredShortcuts(cfg *entity.WorkspaceConfig) { | |||||
| 380 | 381 | ||||
| 381 | func (s *ShortcutSet) registerFloatingProfileShortcutsFromWorkspace(ctx context.Context, workspace *entity.WorkspaceConfig) { | 382 | func (s *ShortcutSet) registerFloatingProfileShortcutsFromWorkspace(ctx context.Context, workspace *entity.WorkspaceConfig) { | ||
| 382 | occupied := make(map[KeyBinding]Action, len(s.Global)) | 383 | occupied := make(map[KeyBinding]Action, len(s.Global)) | ||
| 383 | - | for binding, action := range s.Global { | 384 | + | maps.Copy(occupied, s.Global) |
| 384 | - | occupied[binding] = action | |||
| 385 | - | } | |||
| 386 | 385 | ||||
| 387 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | 386 | for _, shortcut := range collectFloatingProfileShortcutsFromWorkspace(ctx, workspace, occupied) { | ||
| 388 | s.Global[shortcut.Binding] = shortcut.Action | 387 | s.Global[shortcut.Binding] = shortcut.Action | ||
M internal/ui/layout/stacked.go +2 -2
| @@ -39,7 +39,7 @@ type stackedPane struct { | |||
| 39 | 39 | closeButton ButtonWidget // stored for signal disconnection | |
| 40 | 40 | ||
| 41 | 41 | // Retained callback for GestureClick to prevent GC | |
| 42 | - | titleClickCallback interface{} | |
| 42 | + | titleClickCallback any | |
| 43 | 43 | } | |
| 44 | 44 | ||
| 45 | 45 | // StackedView manages a stack of panes where only one is visible at a time. | |
| @@ -183,7 +183,7 @@ func (sv *StackedView) AddPane(ctx context.Context, paneID, title, faviconIconNa | |||
| 183 | 183 | // Returns the retained callback (to prevent GC) and close signal ID for disconnection. | |
| 184 | 184 | func (sv *StackedView) connectTitleBarHandlers( | |
| 185 | 185 | tb titleBarComponents, paneID string, | |
| 186 | - | ) (titleClickCallback interface{}, closeSignalID uint) { | |
| 186 | + | ) (titleClickCallback any, closeSignalID uint) { | |
| 187 | 187 | // Connect title bar click handler using GestureClick | |
| 188 | 188 | // This prevents event propagation issues with nested buttons | |
| 189 | 189 | clickCtrl := gtk.NewGestureClick() | |
| @@ -39,7 +39,7 @@ type stackedPane struct { | |||||
| 39 | closeButton ButtonWidget // stored for signal disconnection | 39 | closeButton ButtonWidget // stored for signal disconnection | ||
| 40 | 40 | ||||
| 41 | // Retained callback for GestureClick to prevent GC | 41 | // Retained callback for GestureClick to prevent GC | ||
| 42 | - | titleClickCallback interface{} | 42 | + | titleClickCallback any |
| 43 | } | 43 | } | ||
| 44 | 44 | ||||
| 45 | // StackedView manages a stack of panes where only one is visible at a time. | 45 | // StackedView manages a stack of panes where only one is visible at a time. | ||
| @@ -183,7 +183,7 @@ func (sv *StackedView) AddPane(ctx context.Context, paneID, title, faviconIconNa | |||||
| 183 | // Returns the retained callback (to prevent GC) and close signal ID for disconnection. | 183 | // Returns the retained callback (to prevent GC) and close signal ID for disconnection. | ||
| 184 | func (sv *StackedView) connectTitleBarHandlers( | 184 | func (sv *StackedView) connectTitleBarHandlers( | ||
| 185 | tb titleBarComponents, paneID string, | 185 | tb titleBarComponents, paneID string, | ||
| 186 | - | ) (titleClickCallback interface{}, closeSignalID uint) { | 186 | + | ) (titleClickCallback any, closeSignalID uint) { |
| 187 | // Connect title bar click handler using GestureClick | 187 | // Connect title bar click handler using GestureClick | ||
| 188 | // This prevents event propagation issues with nested buttons | 188 | // This prevents event propagation issues with nested buttons | ||
| 189 | clickCtrl := gtk.NewGestureClick() | 189 | clickCtrl := gtk.NewGestureClick() | ||
M internal/ui/layout/stacked_test.go +7 -7
| @@ -167,12 +167,12 @@ func TestRemovePane_MiddlePane(t *testing.T) { | |||
| 167 | 167 | containers := make([]*mocks.MockWidget, 3) | |
| 168 | 168 | titleBars := make([]*mocks.MockBoxWidget, 3) | |
| 169 | 169 | ||
| 170 | - | for i := 0; i < 3; i++ { | |
| 170 | + | for i := range 3 { | |
| 171 | 171 | titleBars[i], _, _, containers[i] = setupPaneMocks(t, mockFactory, mockBox) | |
| 172 | 172 | } | |
| 173 | 173 | ||
| 174 | 174 | // Allow any visibility calls during setup and removal | |
| 175 | - | for i := 0; i < 3; i++ { | |
| 175 | + | for i := range 3 { | |
| 176 | 176 | containers[i].EXPECT().SetVisible(mock.Anything).Maybe() | |
| 177 | 177 | titleBars[i].EXPECT().SetVisible(mock.Anything).Maybe() | |
| 178 | 178 | titleBars[i].EXPECT().AddCssClass("active").Maybe() | |
| @@ -263,7 +263,7 @@ func TestSetActive_ValidIndex(t *testing.T) { | |||
| 263 | 263 | containers := make([]*mocks.MockWidget, 2) | |
| 264 | 264 | titleBars := make([]*mocks.MockBoxWidget, 2) | |
| 265 | 265 | ||
| 266 | - | for i := 0; i < 2; i++ { | |
| 266 | + | for i := range 2 { | |
| 267 | 267 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | |
| 268 | 268 | _ = mockFavicon | |
| 269 | 269 | _ = mockLabel | |
| @@ -436,7 +436,7 @@ func TestNavigateNext_WrapsAround(t *testing.T) { | |||
| 436 | 436 | containers := make([]*mocks.MockWidget, 2) | |
| 437 | 437 | titleBars := make([]*mocks.MockBoxWidget, 2) | |
| 438 | 438 | ||
| 439 | - | for i := 0; i < 2; i++ { | |
| 439 | + | for i := range 2 { | |
| 440 | 440 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | |
| 441 | 441 | _ = mockFavicon | |
| 442 | 442 | _ = mockLabel | |
| @@ -473,7 +473,7 @@ func TestNavigatePrevious_WrapsAround(t *testing.T) { | |||
| 473 | 473 | containers := make([]*mocks.MockWidget, 2) | |
| 474 | 474 | titleBars := make([]*mocks.MockBoxWidget, 2) | |
| 475 | 475 | ||
| 476 | - | for i := 0; i < 2; i++ { | |
| 476 | + | for i := range 2 { | |
| 477 | 477 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | |
| 478 | 478 | _ = mockFavicon | |
| 479 | 479 | _ = mockLabel | |
| @@ -657,7 +657,7 @@ func TestInsertPaneAfter_InMiddle(t *testing.T) { | |||
| 657 | 657 | containers := make([]*mocks.MockWidget, 2) | |
| 658 | 658 | titleBars := make([]*mocks.MockBoxWidget, 2) | |
| 659 | 659 | ||
| 660 | - | for i := 0; i < 2; i++ { | |
| 660 | + | for i := range 2 { | |
| 661 | 661 | titleBars[i], _, _, containers[i] = setupPaneMocks(t, mockFactory, mockBox) | |
| 662 | 662 | titleBars[i].EXPECT().SetVisible(mock.Anything).Maybe() | |
| 663 | 663 | containers[i].EXPECT().SetVisible(mock.Anything).Maybe() | |
| @@ -733,7 +733,7 @@ func TestInsertPaneAfter_MaintainsOrder(t *testing.T) { | |||
| 733 | 733 | containers := make([]*mocks.MockWidget, 3) | |
| 734 | 734 | titleBars := make([]*mocks.MockBoxWidget, 3) | |
| 735 | 735 | ||
| 736 | - | for i := 0; i < 3; i++ { | |
| 736 | + | for i := range 3 { | |
| 737 | 737 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | |
| 738 | 738 | _ = mockFavicon | |
| 739 | 739 | _ = mockLabel | |
| @@ -167,12 +167,12 @@ func TestRemovePane_MiddlePane(t *testing.T) { | |||||
| 167 | containers := make([]*mocks.MockWidget, 3) | 167 | containers := make([]*mocks.MockWidget, 3) | ||
| 168 | titleBars := make([]*mocks.MockBoxWidget, 3) | 168 | titleBars := make([]*mocks.MockBoxWidget, 3) | ||
| 169 | 169 | ||||
| 170 | - | for i := 0; i < 3; i++ { | 170 | + | for i := range 3 { |
| 171 | titleBars[i], _, _, containers[i] = setupPaneMocks(t, mockFactory, mockBox) | 171 | titleBars[i], _, _, containers[i] = setupPaneMocks(t, mockFactory, mockBox) | ||
| 172 | } | 172 | } | ||
| 173 | 173 | ||||
| 174 | // Allow any visibility calls during setup and removal | 174 | // Allow any visibility calls during setup and removal | ||
| 175 | - | for i := 0; i < 3; i++ { | 175 | + | for i := range 3 { |
| 176 | containers[i].EXPECT().SetVisible(mock.Anything).Maybe() | 176 | containers[i].EXPECT().SetVisible(mock.Anything).Maybe() | ||
| 177 | titleBars[i].EXPECT().SetVisible(mock.Anything).Maybe() | 177 | titleBars[i].EXPECT().SetVisible(mock.Anything).Maybe() | ||
| 178 | titleBars[i].EXPECT().AddCssClass("active").Maybe() | 178 | titleBars[i].EXPECT().AddCssClass("active").Maybe() | ||
| @@ -263,7 +263,7 @@ func TestSetActive_ValidIndex(t *testing.T) { | |||||
| 263 | containers := make([]*mocks.MockWidget, 2) | 263 | containers := make([]*mocks.MockWidget, 2) | ||
| 264 | titleBars := make([]*mocks.MockBoxWidget, 2) | 264 | titleBars := make([]*mocks.MockBoxWidget, 2) | ||
| 265 | 265 | ||||
| 266 | - | for i := 0; i < 2; i++ { | 266 | + | for i := range 2 { |
| 267 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | 267 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | ||
| 268 | _ = mockFavicon | 268 | _ = mockFavicon | ||
| 269 | _ = mockLabel | 269 | _ = mockLabel | ||
| @@ -436,7 +436,7 @@ func TestNavigateNext_WrapsAround(t *testing.T) { | |||||
| 436 | containers := make([]*mocks.MockWidget, 2) | 436 | containers := make([]*mocks.MockWidget, 2) | ||
| 437 | titleBars := make([]*mocks.MockBoxWidget, 2) | 437 | titleBars := make([]*mocks.MockBoxWidget, 2) | ||
| 438 | 438 | ||||
| 439 | - | for i := 0; i < 2; i++ { | 439 | + | for i := range 2 { |
| 440 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | 440 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | ||
| 441 | _ = mockFavicon | 441 | _ = mockFavicon | ||
| 442 | _ = mockLabel | 442 | _ = mockLabel | ||
| @@ -473,7 +473,7 @@ func TestNavigatePrevious_WrapsAround(t *testing.T) { | |||||
| 473 | containers := make([]*mocks.MockWidget, 2) | 473 | containers := make([]*mocks.MockWidget, 2) | ||
| 474 | titleBars := make([]*mocks.MockBoxWidget, 2) | 474 | titleBars := make([]*mocks.MockBoxWidget, 2) | ||
| 475 | 475 | ||||
| 476 | - | for i := 0; i < 2; i++ { | 476 | + | for i := range 2 { |
| 477 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | 477 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | ||
| 478 | _ = mockFavicon | 478 | _ = mockFavicon | ||
| 479 | _ = mockLabel | 479 | _ = mockLabel | ||
| @@ -657,7 +657,7 @@ func TestInsertPaneAfter_InMiddle(t *testing.T) { | |||||
| 657 | containers := make([]*mocks.MockWidget, 2) | 657 | containers := make([]*mocks.MockWidget, 2) | ||
| 658 | titleBars := make([]*mocks.MockBoxWidget, 2) | 658 | titleBars := make([]*mocks.MockBoxWidget, 2) | ||
| 659 | 659 | ||||
| 660 | - | for i := 0; i < 2; i++ { | 660 | + | for i := range 2 { |
| 661 | titleBars[i], _, _, containers[i] = setupPaneMocks(t, mockFactory, mockBox) | 661 | titleBars[i], _, _, containers[i] = setupPaneMocks(t, mockFactory, mockBox) | ||
| 662 | titleBars[i].EXPECT().SetVisible(mock.Anything).Maybe() | 662 | titleBars[i].EXPECT().SetVisible(mock.Anything).Maybe() | ||
| 663 | containers[i].EXPECT().SetVisible(mock.Anything).Maybe() | 663 | containers[i].EXPECT().SetVisible(mock.Anything).Maybe() | ||
| @@ -733,7 +733,7 @@ func TestInsertPaneAfter_MaintainsOrder(t *testing.T) { | |||||
| 733 | containers := make([]*mocks.MockWidget, 3) | 733 | containers := make([]*mocks.MockWidget, 3) | ||
| 734 | titleBars := make([]*mocks.MockBoxWidget, 3) | 734 | titleBars := make([]*mocks.MockBoxWidget, 3) | ||
| 735 | 735 | ||||
| 736 | - | for i := 0; i < 3; i++ { | 736 | + | for i := range 3 { |
| 737 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | 737 | titleBar, mockFavicon, mockLabel, container := setupPaneMocks(t, mockFactory, mockBox) | ||
| 738 | _ = mockFavicon | 738 | _ = mockFavicon | ||
| 739 | _ = mockLabel | 739 | _ = mockLabel | ||
M internal/ui/pane_window_eject.go +3 -6
| @@ -3,6 +3,7 @@ package ui | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "maps" | |
| 6 | 7 | "strings" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/application/usecase" | |
| @@ -85,17 +86,13 @@ func (a *App) rollbackEjectTargetUIFailure(targetWindow *browserWindow, snapshot | |||
| 85 | 86 | ||
| 86 | 87 | func cloneWindowForTabMap(source map[entity.TabID]*browserWindow) map[entity.TabID]*browserWindow { | |
| 87 | 88 | cloned := make(map[entity.TabID]*browserWindow, len(source)) | |
| 88 | - | for tabID, bw := range source { | |
| 89 | - | cloned[tabID] = bw | |
| 90 | - | } | |
| 89 | + | maps.Copy(cloned, source) | |
| 91 | 90 | return cloned | |
| 92 | 91 | } | |
| 93 | 92 | ||
| 94 | 93 | func cloneWorkspaceViewsMap(source map[entity.TabID]*component.WorkspaceView) map[entity.TabID]*component.WorkspaceView { | |
| 95 | 94 | cloned := make(map[entity.TabID]*component.WorkspaceView, len(source)) | |
| 96 | - | for tabID, wsView := range source { | |
| 97 | - | cloned[tabID] = wsView | |
| 98 | - | } | |
| 95 | + | maps.Copy(cloned, source) | |
| 99 | 96 | return cloned | |
| 100 | 97 | } | |
| 101 | 98 | ||
| @@ -3,6 +3,7 @@ package ui | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "maps" | |||
| 6 | "strings" | 7 | "strings" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/application/usecase" | 9 | "github.com/bnema/dumber/internal/application/usecase" | ||
| @@ -85,17 +86,13 @@ func (a *App) rollbackEjectTargetUIFailure(targetWindow *browserWindow, snapshot | |||||
| 85 | 86 | ||||
| 86 | func cloneWindowForTabMap(source map[entity.TabID]*browserWindow) map[entity.TabID]*browserWindow { | 87 | func cloneWindowForTabMap(source map[entity.TabID]*browserWindow) map[entity.TabID]*browserWindow { | ||
| 87 | cloned := make(map[entity.TabID]*browserWindow, len(source)) | 88 | cloned := make(map[entity.TabID]*browserWindow, len(source)) | ||
| 88 | - | for tabID, bw := range source { | 89 | + | maps.Copy(cloned, source) |
| 89 | - | cloned[tabID] = bw | |||
| 90 | - | } | |||
| 91 | return cloned | 90 | return cloned | ||
| 92 | } | 91 | } | ||
| 93 | 92 | ||||
| 94 | func cloneWorkspaceViewsMap(source map[entity.TabID]*component.WorkspaceView) map[entity.TabID]*component.WorkspaceView { | 93 | func cloneWorkspaceViewsMap(source map[entity.TabID]*component.WorkspaceView) map[entity.TabID]*component.WorkspaceView { | ||
| 95 | cloned := make(map[entity.TabID]*component.WorkspaceView, len(source)) | 94 | cloned := make(map[entity.TabID]*component.WorkspaceView, len(source)) | ||
| 96 | - | for tabID, wsView := range source { | 95 | + | maps.Copy(cloned, source) |
| 97 | - | cloned[tabID] = wsView | |||
| 98 | - | } | |||
| 99 | return cloned | 96 | return cloned | ||
| 100 | } | 97 | } | ||
| 101 | 98 | ||||
M internal/ui/systemviews/app_test.go +1 -2
| @@ -404,8 +404,7 @@ func TestAppCloseStopsActionWorkerAndReleasesDOM(t *testing.T) { | |||
| 404 | 404 | history := &recordingHistoryService{entries: []*entity.HistoryEntry{{ID: 1, URL: "https://example.com"}}} | |
| 405 | 405 | app := NewApp(Dependencies{DOM: dom, History: history, LocationURI: "dumb://history"}) | |
| 406 | 406 | ||
| 407 | - | ctx, cancel := context.WithCancel(context.Background()) | |
| 408 | - | defer cancel() | |
| 407 | + | ctx := t.Context() | |
| 409 | 408 | require.NoError(t, app.RunWithContext(ctx)) | |
| 410 | 409 | require.NotNil(t, dom.handler) | |
| 411 | 410 | ||
| @@ -404,8 +404,7 @@ func TestAppCloseStopsActionWorkerAndReleasesDOM(t *testing.T) { | |||||
| 404 | history := &recordingHistoryService{entries: []*entity.HistoryEntry{{ID: 1, URL: "https://example.com"}}} | 404 | history := &recordingHistoryService{entries: []*entity.HistoryEntry{{ID: 1, URL: "https://example.com"}}} | ||
| 405 | app := NewApp(Dependencies{DOM: dom, History: history, LocationURI: "dumb://history"}) | 405 | app := NewApp(Dependencies{DOM: dom, History: history, LocationURI: "dumb://history"}) | ||
| 406 | 406 | ||||
| 407 | - | ctx, cancel := context.WithCancel(context.Background()) | 407 | + | ctx := t.Context() |
| 408 | - | defer cancel() | |||
| 409 | require.NoError(t, app.RunWithContext(ctx)) | 408 | require.NoError(t, app.RunWithContext(ctx)) | ||
| 410 | require.NotNil(t, dom.handler) | 409 | require.NotNil(t, dom.handler) | ||
| 411 | 410 | ||||
M internal/ui/systemviews/config_actions.go +2 -3
| @@ -3,6 +3,7 @@ package systemviews | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "maps" | |
| 6 | 7 | "math" | |
| 7 | 8 | "net/url" | |
| 8 | 9 | "strconv" | |
| @@ -413,9 +414,7 @@ func webUIConfigFromPayload(payload dto.SystemviewConfigPayload) dto.WebUIConfig | |||
| 413 | 414 | ||
| 414 | 415 | func cloneSearchShortcuts(shortcuts map[string]dto.SearchShortcut) map[string]dto.SearchShortcut { | |
| 415 | 416 | clone := make(map[string]dto.SearchShortcut, len(shortcuts)) | |
| 416 | - | for key, shortcut := range shortcuts { | |
| 417 | - | clone[key] = shortcut | |
| 418 | - | } | |
| 417 | + | maps.Copy(clone, shortcuts) | |
| 419 | 418 | return clone | |
| 420 | 419 | } | |
| 421 | 420 | ||
| @@ -3,6 +3,7 @@ package systemviews | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "maps" | |||
| 6 | "math" | 7 | "math" | ||
| 7 | "net/url" | 8 | "net/url" | ||
| 8 | "strconv" | 9 | "strconv" | ||
| @@ -413,9 +414,7 @@ func webUIConfigFromPayload(payload dto.SystemviewConfigPayload) dto.WebUIConfig | |||||
| 413 | 414 | ||||
| 414 | func cloneSearchShortcuts(shortcuts map[string]dto.SearchShortcut) map[string]dto.SearchShortcut { | 415 | func cloneSearchShortcuts(shortcuts map[string]dto.SearchShortcut) map[string]dto.SearchShortcut { | ||
| 415 | clone := make(map[string]dto.SearchShortcut, len(shortcuts)) | 416 | clone := make(map[string]dto.SearchShortcut, len(shortcuts)) | ||
| 416 | - | for key, shortcut := range shortcuts { | 417 | + | maps.Copy(clone, shortcuts) |
| 417 | - | clone[key] = shortcut | |||
| 418 | - | } | |||
| 419 | return clone | 418 | return clone | ||
| 420 | } | 419 | } | ||
| 421 | 420 | ||||
M internal/ui/theme/history_sidebar_css_test.go +3 -3
| @@ -91,11 +91,11 @@ func TestGenerateHistorySidebarCSS_NoEmptyCSSBlocks(t *testing.T) { | |||
| 91 | 91 | if i == 0 { | |
| 92 | 92 | continue | |
| 93 | 93 | } | |
| 94 | - | closeIdx := strings.Index(section, "}") | |
| 95 | - | if closeIdx < 0 { | |
| 94 | + | before, _, ok := strings.Cut(section, "}") | |
| 95 | + | if !ok { | |
| 96 | 96 | continue | |
| 97 | 97 | } | |
| 98 | - | blockContent := strings.TrimSpace(section[:closeIdx]) | |
| 98 | + | blockContent := strings.TrimSpace(before) | |
| 99 | 99 | assert.NotEmpty(t, blockContent, "CSS block must not be empty: section %d", i) | |
| 100 | 100 | } | |
| 101 | 101 | } | |
| @@ -91,11 +91,11 @@ func TestGenerateHistorySidebarCSS_NoEmptyCSSBlocks(t *testing.T) { | |||||
| 91 | if i == 0 { | 91 | if i == 0 { | ||
| 92 | continue | 92 | continue | ||
| 93 | } | 93 | } | ||
| 94 | - | closeIdx := strings.Index(section, "}") | 94 | + | before, _, ok := strings.Cut(section, "}") |
| 95 | - | if closeIdx < 0 { | 95 | + | if !ok { |
| 96 | continue | 96 | continue | ||
| 97 | } | 97 | } | ||
| 98 | - | blockContent := strings.TrimSpace(section[:closeIdx]) | 98 | + | blockContent := strings.TrimSpace(before) |
| 99 | assert.NotEmpty(t, blockContent, "CSS block must not be empty: section %d", i) | 99 | assert.NotEmpty(t, blockContent, "CSS block must not be empty: section %d", i) | ||
| 100 | } | 100 | } | ||
| 101 | } | 101 | } | ||
M internal/ui/window/main_window_sidebar.go +1 -7
| @@ -45,13 +45,7 @@ func (mw *MainWindow) SetSidebarWidth(cfg SidebarWidthConfig) { | |||
| 45 | 45 | mw.logger.Warn().Int("min_px", cfg.MinPx).Int("max_px", cfg.MaxPx).Msg("invalid sidebar width bounds; swapping") | |
| 46 | 46 | cfg.MinPx, cfg.MaxPx = cfg.MaxPx, cfg.MinPx | |
| 47 | 47 | } | |
| 48 | - | clamped := cfg.WidthPx | |
| 49 | - | if clamped < cfg.MinPx { | |
| 50 | - | clamped = cfg.MinPx | |
| 51 | - | } | |
| 52 | - | if clamped > cfg.MaxPx { | |
| 53 | - | clamped = cfg.MaxPx | |
| 54 | - | } | |
| 48 | + | clamped := min(max(cfg.WidthPx, cfg.MinPx), cfg.MaxPx) | |
| 55 | 49 | mw.sidebarBox.SetSizeRequest(clamped, -1) | |
| 56 | 50 | mw.logger.Debug().Int("sidebar_width", clamped).Msg("sidebar width set") | |
| 57 | 51 | } | |
| @@ -45,13 +45,7 @@ func (mw *MainWindow) SetSidebarWidth(cfg SidebarWidthConfig) { | |||||
| 45 | mw.logger.Warn().Int("min_px", cfg.MinPx).Int("max_px", cfg.MaxPx).Msg("invalid sidebar width bounds; swapping") | 45 | mw.logger.Warn().Int("min_px", cfg.MinPx).Int("max_px", cfg.MaxPx).Msg("invalid sidebar width bounds; swapping") | ||
| 46 | cfg.MinPx, cfg.MaxPx = cfg.MaxPx, cfg.MinPx | 46 | cfg.MinPx, cfg.MaxPx = cfg.MaxPx, cfg.MinPx | ||
| 47 | } | 47 | } | ||
| 48 | - | clamped := cfg.WidthPx | 48 | + | clamped := min(max(cfg.WidthPx, cfg.MinPx), cfg.MaxPx) |
| 49 | - | if clamped < cfg.MinPx { | |||
| 50 | - | clamped = cfg.MinPx | |||
| 51 | - | } | |||
| 52 | - | if clamped > cfg.MaxPx { | |||
| 53 | - | clamped = cfg.MaxPx | |||
| 54 | - | } | |||
| 55 | mw.sidebarBox.SetSizeRequest(clamped, -1) | 49 | mw.sidebarBox.SetSizeRequest(clamped, -1) | ||
| 56 | mw.logger.Debug().Int("sidebar_width", clamped).Msg("sidebar width set") | 50 | mw.logger.Debug().Int("sidebar_width", clamped).Msg("sidebar width set") | ||
| 57 | } | 51 | } | ||