Added stable `key` parameters to `items(...)` and `itemsIndexed(...)` calls across 13 screens. This prevents Compose from treating all list mutations as full item replacements and eliminates unnecessary full list recompositions during state updates.
Modifications applied to:
- KeysScreen.kt, SharedComponents.kt, AchievementsScreen.kt, PluginsScreen.kt
- SessionsScreen.kt, CronJobsScreen.kt, McpServersScreen.kt, PairingScreen.kt
- ToolsetsScreen.kt, ChatScreen.kt, ModelScreen.kt, SkillsScreen.kt
- KanbanScreen.kt, ProfilesScreen.kt, WebhooksScreen.kt
Note: LogsScreen.kt was left without a key because logs are string lines that can be duplicated, and using `.hashCode()` would cause a "Key was already used" crash. Positional indexing is safer there.
Fixes#279
- Extracted `safeLaunchLoad` inline extension function to `ViewModelExt.kt` to encapsulate API call success/failure handling logic and replaced usages across 18 viewmodels.
- Extracted `ToastHost` interface to standardize `clearToast()` behavior across all ViewModels displaying toasts.
- Extracted `ToastEffect` composable to `SharedComponents.kt` and applied it to 16 screens, reducing repetitive side effect code blocks.
Fixes#286
* fix(#220): implement edit soul and set model dialogs
* fix(#211): make UI more compact and information dense
* fix(#211): make navigation drawer sidebar more compact
* feat(#252): move settings item to end of navigation drawer
* feat(#250): add bottom navigation bar display mode settings
* feat(#225): add search bar filtering to remaining screens
* fix: make bottom navigation bar display modes more compact
* fix: resolve compilation error in KanbanScreen by referencing assignedTo
* feat: add search to achievements screen and top padding for icon-only navigation bar
* fix: adjust icon-only bottom bar height and remove padding
* fix(#249): remove unused PaddingValues imports in 6 screens
---------
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
- HermesScaffold: added isRefreshing param, wraps content in PullToRefreshBox when onRefresh is set
- All 16 data screens: pass isRefreshing = state.isLoading
- Initial loading only shows full-screen LoadingState when data is empty (no spinner over existing content)
- AchievementsScreen already had the correct pattern, just needed isRefreshing param
Co-authored-by: M57 <hy4ri@users.noreply.github.com>
- HermesScaffold: title now @Composable () -> Unit (M3-idiomatic)
- HermesScaffold: added modifier and snackbarHost parameters
- Updated all 18 callers from title="X" to title={Text("X")}
- ChatScreen: converted from raw Scaffold to HermesScaffold
- Removed unused Menu icon import (ktlint fix)
Complete UI/UX rewrite of hermes-mobile with Material You design system.
Phase 1: Color/Theme/Type/Shapes/Spacing/Motion tokens
Phase 2: HermesScaffold, StateViews (skeleton/empty/error), SearchBar, StatusBadge, ToggleRow
Phase 3: Bottom nav + drawer navigation with NavigationController
Phase 4: All 17 screens rewritten with HermesScaffold and Material You patterns
Closes PR #3.