Let users customize library tabs - #1799
Conversation
📝 WalkthroughWalkthroughAdds persistent library-tab visibility and ordering preferences. Navigation uses visible tabs. The library UI provides responsive controls for visibility, ordering, reset, previews, counts, and accessibility actions. ChangesLibrary tab customization
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant LibraryTabBar
participant LibraryTabsCustomizer
participant LibraryViewModel
participant PrefManager
User->>LibraryTabBar: select customize action
LibraryTabBar->>LibraryTabsCustomizer: open customizer
User->>LibraryTabsCustomizer: change visibility or order
LibraryTabsCustomizer->>LibraryViewModel: submit preference change
LibraryViewModel->>PrefManager: persist normalized preferences
LibraryViewModel-->>LibraryTabBar: update visible tabs
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.planning/sketches/001-store-tab-customization/index.html:
- Around line 101-103: Update the compact-layout rules for .move-controls so
narrow-screen users retain an accessible tab-reorder action; either keep the
move buttons visible below 700px or implement equivalent touch and keyboard
reordering before hiding them. Preserve the existing .store-row and .sheet
responsive styling.
In @.planning/sketches/themes/default.css:
- Line 13: Update the --font-sans declaration to quote the case-sensitive font
family names Inter and BlinkMacSystemFont, preserving the existing fallback
order and all other stack entries.
In `@app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt`:
- Around line 343-353: Update LibraryViewModel.onLibraryTabMoved to reject
hidden tabs and move the selected tab relative to adjacent visible, non-ALL
preferences rather than raw list indices; add coverage for hidden preferences
between visible tabs. In LibraryTabsCustomizer, calculate canMoveUp and
canMoveDown from visible non-ALL preferences and disable reorder controls for
hidden tabs.
In
`@app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt`:
- Around line 444-447: Add a semantics content description to the Switch in the
LibraryTabsCustomizer tab row using the tab’s localized label resource, while
preserving its existing checked state and onCheckedChange behavior.
In `@app/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.kt`:
- Around line 477-479: Update the parent key handler and global key listener
guards in LibraryScreen so B, L1, and R1 navigation/focus-bootstrap commands are
blocked while isTabCustomizerOpen is true. Handle B by dismissing the tab
customizer first, and add !isTabCustomizerOpen to every relevant
library-navigation and focus-bootstrap condition while preserving normal
behavior when the customizer is closed.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 73a94995-c660-4d89-b2d7-8429c975e96d
📒 Files selected for processing (14)
.planning/sketches/001-store-tab-customization/PLAN.md.planning/sketches/001-store-tab-customization/README.md.planning/sketches/001-store-tab-customization/index.html.planning/sketches/MANIFEST.md.planning/sketches/themes/default.cssapp/src/main/java/app/gamenative/PrefManager.ktapp/src/main/java/app/gamenative/ui/data/LibraryState.ktapp/src/main/java/app/gamenative/ui/enums/LibraryTab.ktapp/src/main/java/app/gamenative/ui/model/LibraryViewModel.ktapp/src/main/java/app/gamenative/ui/screen/library/LibraryScreen.ktapp/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.ktapp/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.ktapp/src/main/res/values/strings.xmlapp/src/test/java/app/gamenative/ui/enums/LibraryTabTest.kt
| .store-row { grid-template-columns: 25px 1fr auto; } | ||
| .move-controls { display: none; } | ||
| .sheet { max-height: 90%; } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep a reorder action on compact layouts.
Lines 101-102 hide the move buttons below 700px. The drag indicator has no drag implementation. Users on narrow screens cannot reorder tabs.
Keep accessible move buttons, or implement touch and keyboard reorder behavior before hiding them.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.planning/sketches/001-store-tab-customization/index.html around lines 101 -
103, Update the compact-layout rules for .move-controls so narrow-screen users
retain an accessible tab-reorder action; either keep the move buttons visible
below 700px or implement equivalent touch and keyboard reordering before hiding
them. Preserve the existing .store-row and .sheet responsive styling.
| --color-accent: #00d4ff; | ||
| --color-success: #10b981; | ||
| --color-danger: #ef4444; | ||
| --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the Stylelint errors in the font stack.
Stylelint reports Inter and BlinkMacSystemFont as invalidly cased keywords. Quote these font family names, or exclude this token from the configured rule.
Proposed fix
- --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "BlinkMacSystemFont", "Segoe UI", sans-serif;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| --font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, "BlinkMacSystemFont", "Segoe UI", sans-serif; |
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 13-13: Expected "Inter" to be "inter" (value-keyword-case)
(value-keyword-case)
[error] 13-13: Expected "BlinkMacSystemFont" to be "blinkmacsystemfont" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.planning/sketches/themes/default.css at line 13, Update the --font-sans
declaration to quote the case-sensitive font family names Inter and
BlinkMacSystemFont, preserving the existing fallback order and all other stack
entries.
Source: Linters/SAST tools
| fun onLibraryTabMoved(tab: LibraryTab, offset: Int) { | ||
| if (tab == LibraryTab.ALL || offset == 0) return | ||
| updateLibraryTabPreferences { preferences -> | ||
| val mutable = preferences.toMutableList() | ||
| val currentIndex = mutable.indexOfFirst { it.tab == tab } | ||
| if (currentIndex < 1) return@updateLibraryTabPreferences preferences | ||
| val targetIndex = (currentIndex + offset).coerceIn(1, mutable.lastIndex) | ||
| if (targetIndex == currentIndex) return@updateLibraryTabPreferences preferences | ||
| val item = mutable.removeAt(currentIndex) | ||
| mutable.add(targetIndex, item) | ||
| mutable |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Move tabs relative to visible tabs only.
Hidden tabs currently consume movement positions. For example, with ALL, STEAM, !GOG, EPIC, moving EPIC up moves it across hidden GOG but leaves the visible order unchanged. The preview and tab bar therefore do not change after an enabled move action.
app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt#L343-L353: reject moves for hidden tabs. Find the adjacent visible non-ALLpreference, then move relative to that preference.app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt#L289-L297: calculatecanMoveUpandcanMoveDownfrom visible non-ALLpreferences. Disable reorder controls for hidden tabs.- Add a test with hidden preferences between two visible tabs.
The PR objective requires users to reorder visible tabs.
📍 Affects 2 files
app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt#L343-L353(this comment)app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt#L289-L297
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/src/main/java/app/gamenative/ui/model/LibraryViewModel.kt` around lines
343 - 353, Update LibraryViewModel.onLibraryTabMoved to reject hidden tabs and
move the selected tab relative to adjacent visible, non-ALL preferences rather
than raw list indices; add coverage for hidden preferences between visible tabs.
In LibraryTabsCustomizer, calculate canMoveUp and canMoveDown from visible
non-ALL preferences and disable reorder controls for hidden tabs.
| Switch( | ||
| checked = preference.isVisible, | ||
| onCheckedChange = { onVisibilityChanged(tab, it) }, | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt"
if [ -f "$file" ]; then
echo "== file lines =="
sed -n '400,470p' "$file" | nl -ba -v400
echo
echo "== surrounding imports/usages =="
rg -n "LibraryTabPreferenceRow|tabLabelResId|labelResId|Switch\\(" app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt
else
echo "file not found"
echo "candidates:"
fd -i 'LibraryTabsCustomizer.kt' .
fiRepository: utkarshdalal/GameNative
Length of output: 218
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt"
if [ -f "$file" ]; then
echo "== file exists =="
wc -l "$file"
echo
echo "== lines 400-470 =="
sed -n '400,470p' "$file" | awk '{printf "%6d\t%s\n", NR+399, $0}'
echo
echo "== relevant symbols/usages =="
rg -n "LibraryTabPreferenceRow|labelResId|Switch\\(|semantics\\(|Modifier\\." app/src/main/java/app/gamenative/ui/screen/library/components/$file
else
echo "file not found"
fd -i 'LibraryTabsCustomizer.kt' .
fiRepository: utkarshdalal/GameNative
Length of output: 2987
Label the visibility switch with the tab name.
The Switch has no semantic label, so screen readers can expose it separately from the row text without identifying which tab it controls. Add a semantics label, such as stringResource(tab.labelResId), to the Switch while preserving its checked-state semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabsCustomizer.kt`
around lines 444 - 447, Add a semantics content description to the Switch in the
LibraryTabsCustomizer tab row using the tab’s localized label resource, while
preserving its existing checked state and onCheckedChange behavior.
Persist tab visibility and ordering separately from library source filters, add an adaptive customization surface, and keep controller traversal aligned with visible tabs.
Use a dedicated two-column dialog on landscape handhelds while retaining the compact bottom sheet on narrow screens.
There was a problem hiding this comment.
2 issues found across 14 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt">
<violation number="1" location="app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt:75">
P2: Reordering the selected tab can leave it off-screen because the centering effect only observes `currentTab`, not the new tab order. Include the tab list or `currentIndex` in the effect key so customization re-centers the selected tab.</violation>
</file>
<file name=".planning/sketches/001-store-tab-customization/index.html">
<violation number="1" location=".planning/sketches/001-store-tab-customization/index.html:101">
P1: On viewports at or below 700px, users cannot reorder tabs: the arrow controls are hidden and the drag handle is noninteractive. Keep a usable reorder control on mobile (or implement drag reordering) so the promised customization works on phones.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| @media (max-width: 700px) { | ||
| .game-grid { grid-template-columns: repeat(2,minmax(0,1fr)); } | ||
| .library-head, .tab-shell, .game-grid { padding-left: 14px; padding-right: 14px; } | ||
| .store-row { grid-template-columns: 25px 1fr auto; } |
There was a problem hiding this comment.
P1: On viewports at or below 700px, users cannot reorder tabs: the arrow controls are hidden and the drag handle is noninteractive. Keep a usable reorder control on mobile (or implement drag reordering) so the promised customization works on phones.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .planning/sketches/001-store-tab-customization/index.html, line 101:
<comment>On viewports at or below 700px, users cannot reorder tabs: the arrow controls are hidden and the drag handle is noninteractive. Keep a usable reorder control on mobile (or implement drag reordering) so the promised customization works on phones.</comment>
<file context>
@@ -0,0 +1,294 @@
+ @media (max-width: 700px) {
+ .game-grid { grid-template-columns: repeat(2,minmax(0,1fr)); }
+ .library-head, .tab-shell, .game-grid { padding-left: 14px; padding-right: 14px; }
+ .store-row { grid-template-columns: 25px 1fr auto; }
+ .move-controls { display: none; }
+ .sheet { max-height: 90%; }
</file context>
| @Composable | ||
| fun LibraryTabBar( | ||
| currentTab: LibraryTab, | ||
| tabs: List<LibraryTab>, |
There was a problem hiding this comment.
P2: Reordering the selected tab can leave it off-screen because the centering effect only observes currentTab, not the new tab order. Include the tab list or currentIndex in the effect key so customization re-centers the selected tab.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/screen/library/components/LibraryTabBar.kt, line 75:
<comment>Reordering the selected tab can leave it off-screen because the centering effect only observes `currentTab`, not the new tab order. Include the tab list or `currentIndex` in the effect key so customization re-centers the selected tab.</comment>
<file context>
@@ -71,12 +72,14 @@ import app.gamenative.ui.util.rememberWindowWidthClass
@Composable
fun LibraryTabBar(
currentTab: LibraryTab,
+ tabs: List<LibraryTab>,
tabCounts: Map<LibraryTab, Int>,
onTabSelected: (LibraryTab) -> Unit,
</file context>
4718906 to
735a15d
Compare


Hi, this adds a small option to choose which store tabs appear in the library.
Users can hide stores they do not use and reorder the remaining tabs. The All tab stays visible, and hidden stores still appear in All.
The editor adapts to phones and landscape handhelds. I tested it in portrait on a Galaxy Z Flip and in landscape on a Retroid Pocket 5.
I hope this makes the library a little cleaner for people who only use a few stores.
Showcase
Retroid Pocket 5, landscape
Galaxy Z Flip, portrait
Videos
signal-2026-08-08-17-29-15-808.mp4
signal-2026-08-08-17-30-05-577.mp4
Summary by cubic
Let users show, hide, and reorder library tabs, with All always visible. Preferences persist independently from source filters, and tab navigation only cycles through visible tabs.
LibraryTabsCustomizerwith live preview: bottom sheet on compact and a two‑column dialog on wide screens; includes switches, move up/down, long‑press drag, and Reset.PrefManager.libraryTabPreferencesandLibraryTabPreference; normalize/serialize to keepALLfirst/visible, drop unsupported values, and auto‑add new tabs.LibraryViewModelto toggle visibility, reorder tabs, reset to defaults, ignore selection of hidden tabs, and fall back toALLif the active tab is hidden.LibraryTabBarnow takes atabslist and shows an Edit button to open the customizer; touch, controller, and accessibility friendly.visibleLibraryTabs; added unit tests for normalization, preference round‑trip, and traversal behavior.Written for commit 735a15d. Summary will update on new commits.
Summary by CodeRabbit
New Features
Accessibility
Bug Fixes