Commit 4e44eab
[Agent] perf(ui): SwiftUI performance audit — reduce scroll stutter (#3480)
* perf(ui): SwiftUI performance audit — reduce scroll stutter and Realm overhead
* perf(ui): eliminate per-frame re-renders and lazy-collection misuse
- ConsoleGamesView: ForEach now uses Results<PVGame> directly instead
of .toArray().filter{} — avoids O(n) array allocation every render
- CustomPageIndicator: remove GeometryReader wrapper; use
.frame(maxWidth:.infinity) instead to avoid per-frame layout pass
- HomeContinueSection: cache PVSaveState footer lookup in @State;
only refresh on page/focus change, not every body re-render
- HomeView: move scroll offset tracking into a reference-type
ScrollTracker so previousOffset mutations never trigger HomeView
body re-evaluation; isSearchBarVisible written only when it changes
- GameItemView: cache discCount (relatedFiles.toArray()) on onAppear
instead of re-computing on every body evaluation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): address Copilot review — bounded concurrency and search task cleanup
- Cap concurrent Realm reloads to 4 in refreshFromRealmChanges() to
prevent thread/memory spikes on large libraries
- Apply same bounded concurrency cap to loadAllGames() startup load
- Cancel searchDebounceTask on .onSubmit to avoid redundant post-submit
query; also cancel on .onDisappear to prevent stale background tasks
- Trigger loadGamesIfNeeded for all systems in TVMediaSystemsView.task
so systems not yet loaded appear in the grid without depending on the
lazy-load side effect of shelf rows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(ui): address Copilot review — scroll perf & state safety
- ConsoleGamesView: use absoluteString for view identity to avoid
Optional(...) in the .id() string
- TVMediaMainView: build ordered refresh list (selected system first),
add defer to reset isLoading on cancellation, update stale comment
in systemsWithGames
- HomeContinueSection: centre indicators without ScrollView when they
fit; only use ScrollView when pages > maxVisibleIndicators
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address Copilot review — stable view IDs, ScrollTracker, disc count
- ConsoleGamesView: use trueArtworkURL?.absoluteString instead of
optional string interpolation (both PVGame call sites)
- TVMediaMainView: task(id:) now uses full system identifiers list,
not just count, to catch same-count replacements/reorders
- HomeView: drop ObservableObject from ScrollTracker; hold via @State
to eliminate unused subscription overhead
- GameItemView: add onChange(of: relatedFiles.count) to keep
cachedDiscCount in sync when files are imported while cell is visible
- changelog: reflect all fixes accurately
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address Copilot round-3 review — optional URL IDs and actor clarity
- Fix remaining 4 ConsoleGamesView call sites that still used
\`\(game.trueArtworkURL)\` (Optional interpolation); all cell .id
modifiers now use \`trueArtworkURL?.absoluteString ?? ""\`
- Clarify thread-safety in loadGamesForSystemAsync: remove redundant
\`await MainActor.run\` (fn is already @mainactor) and add doc comment
explaining actor isolation for concurrent TaskGroup callers
- Update changelog to reflect all call sites fixed and actor change
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address Copilot review — icon reactivity, focus, disc-count dedup
- TVMediaSystemsView: make icon-load task reactive to systemsWithGames
changes (previously ran once on appear when cache may be empty)
- TVMediaSystemsView: assign focusedSystemID via onChange when first
systems appear, not only onAppear when list may still be empty
- GameItemView: extract refreshCachedDiscCount() to eliminate duplicated
disc-count computation between onAppear and onChange handlers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use [String] identity for icon-load task to avoid join collisions
.map(\.identifier).joined() can produce the same string for
different identifier arrays (e.g. ["ab","c"] == ["a","bc"]).
[String] is Hashable/Equatable and correctly distinguishes all arrays.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use defer to reset isLoading on task cancellation
Ensures TVMediaSystemDetailView's loading state resets even when the
.task is cancelled (e.g. view disappears before loadContent finishes).
Consistent with the defer pattern already used in loadAllGames.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 parent f477d54 commit 4e44eab
7 files changed
Lines changed: 360 additions & 181 deletions
File tree
- .changelog
- PVUI/Sources
- PVSwiftUI
- Consoles
- Home
- tvOS Media UI
- PVUIBase/SwiftUI/GameItem
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
885 | 885 | | |
886 | 886 | | |
887 | 887 | | |
888 | | - | |
| 888 | + | |
889 | 889 | | |
890 | 890 | | |
891 | 891 | | |
| |||
924 | 924 | | |
925 | 925 | | |
926 | 926 | | |
927 | | - | |
928 | | - | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
929 | 932 | | |
930 | 933 | | |
931 | 934 | | |
| |||
948 | 951 | | |
949 | 952 | | |
950 | 953 | | |
951 | | - | |
952 | | - | |
953 | | - | |
954 | | - | |
955 | | - | |
956 | | - | |
957 | | - | |
958 | | - | |
959 | | - | |
960 | | - | |
961 | | - | |
962 | | - | |
963 | | - | |
964 | | - | |
965 | | - | |
966 | | - | |
967 | | - | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
968 | 973 | | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
969 | 978 | | |
970 | | - | |
971 | | - | |
972 | | - | |
973 | | - | |
974 | 979 | | |
975 | | - | |
976 | | - | |
977 | | - | |
978 | | - | |
979 | | - | |
980 | | - | |
981 | | - | |
982 | | - | |
983 | | - | |
984 | | - | |
985 | | - | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
986 | 990 | | |
987 | | - | |
| 991 | + | |
988 | 992 | | |
| 993 | + | |
989 | 994 | | |
990 | 995 | | |
991 | 996 | | |
| |||
1019 | 1024 | | |
1020 | 1025 | | |
1021 | 1026 | | |
1022 | | - | |
| 1027 | + | |
1023 | 1028 | | |
1024 | 1029 | | |
1025 | 1030 | | |
| |||
1060 | 1065 | | |
1061 | 1066 | | |
1062 | 1067 | | |
1063 | | - | |
1064 | | - | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
1065 | 1073 | | |
1066 | 1074 | | |
1067 | 1075 | | |
| |||
1083 | 1091 | | |
1084 | 1092 | | |
1085 | 1093 | | |
1086 | | - | |
1087 | | - | |
1088 | | - | |
1089 | | - | |
1090 | | - | |
1091 | | - | |
1092 | | - | |
1093 | | - | |
1094 | | - | |
1095 | | - | |
1096 | | - | |
1097 | | - | |
1098 | | - | |
1099 | | - | |
1100 | | - | |
1101 | | - | |
1102 | | - | |
1103 | | - | |
1104 | | - | |
1105 | | - | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
1106 | 1116 | | |
1107 | | - | |
| 1117 | + | |
1108 | 1118 | | |
| 1119 | + | |
1109 | 1120 | | |
1110 | 1121 | | |
1111 | 1122 | | |
| |||
1222 | 1233 | | |
1223 | 1234 | | |
1224 | 1235 | | |
1225 | | - | |
1226 | | - | |
| 1236 | + | |
1227 | 1237 | | |
1228 | 1238 | | |
1229 | 1239 | | |
| |||
1598 | 1608 | | |
1599 | 1609 | | |
1600 | 1610 | | |
1601 | | - | |
1602 | | - | |
| 1611 | + | |
1603 | 1612 | | |
1604 | 1613 | | |
1605 | 1614 | | |
| |||
0 commit comments