diff --git a/.Jules/changelog.md b/.Jules/changelog.md
index 11fc864..3cbc081 100644
--- a/.Jules/changelog.md
+++ b/.Jules/changelog.md
@@ -7,6 +7,15 @@
## [Unreleased]
### Added
+- **Mobile Skeleton Loading:** Implemented skeleton loading states for the Home screen group list.
+ - **Features:**
+ - Replaced `ActivityIndicator` with a list of `GroupCardSkeleton` components.
+ - Mimics the exact layout of the group cards (Avatar + Title + Subtitle).
+ - Uses `Animated` for a smooth pulsing opacity effect.
+ - Automatically adapts to light/dark mode using `react-native-paper` theme.
+ - Accessible container with `accessibilityLabel="Loading groups"`.
+ - **Technical:** Created `mobile/components/ui/Skeleton.js` and `mobile/components/skeletons/GroupCardSkeleton.js`. Integrated into `mobile/screens/HomeScreen.js`.
+
- **Password Strength Meter:** Added a visual password strength indicator to the signup form.
- **Features:**
- Real-time strength calculation (Length, Uppercase, Lowercase, Number, Symbol).
diff --git a/.Jules/knowledge.md b/.Jules/knowledge.md
index 43a9ab0..902307e 100644
--- a/.Jules/knowledge.md
+++ b/.Jules/knowledge.md
@@ -317,6 +317,31 @@ When building mobile screens with React Native Paper:
3. **Hints:** Use `accessibilityHint` for non-obvious actions (e.g., "Double tap to delete").
4. **State:** For custom checkboxes or toggles, use `accessibilityState={{ checked: boolean }}`.
+### Skeleton Loading Pattern
+
+**Date:** 2026-02-12
+**Context:** Implementing HomeScreen skeletons
+
+To create professional loading states in React Native:
+
+1. **Base Component:** Create a `Skeleton` component using `Animated` for pulsing opacity.
+ * Use `useTheme` to set background color (e.g., `theme.colors.surfaceVariant`).
+ * Use `useNativeDriver: true` for performance.
+2. **Layout Mirroring:** Create a specific skeleton component (e.g., `GroupCardSkeleton`) that mirrors the actual component's layout using `Card`, `View`, and spacing.
+3. **Integration:**
+ * Replace `ActivityIndicator` with a list of skeletons.
+ * Wrap in a `View` with `accessible={true}` and `accessibilityLabel="Loading..."`.
+
+```javascript
+// Animation Logic
+Animated.loop(
+ Animated.sequence([
+ Animated.timing(opacity, { toValue: 0.7, duration: 800, useNativeDriver: true }),
+ Animated.timing(opacity, { toValue: 0.3, duration: 800, useNativeDriver: true }),
+ ])
+).start();
+```
+
---
## API Response Patterns
diff --git a/.Jules/todo.md b/.Jules/todo.md
index ebb0c7a..6e71fd5 100644
--- a/.Jules/todo.md
+++ b/.Jules/todo.md
@@ -57,11 +57,12 @@
- Impact: Native feel, users can easily refresh data
- Size: ~150 lines
-- [ ] **[ux]** Complete skeleton loading for HomeScreen groups
- - File: `mobile/screens/HomeScreen.js`
+- [x] **[ux]** Complete skeleton loading for HomeScreen groups
+ - Completed: 2026-02-12
+ - Files: `mobile/screens/HomeScreen.js`, `mobile/components/ui/Skeleton.js`, `mobile/components/skeletons/GroupCardSkeleton.js`
- Context: Replace ActivityIndicator with skeleton group cards
- Impact: Better loading experience, less jarring
- - Size: ~40 lines
+ - Size: ~80 lines
- Added: 2026-01-01
- [x] **[a11y]** Complete accessibility labels for all screens
diff --git a/mobile/components/skeletons/GroupCardSkeleton.js b/mobile/components/skeletons/GroupCardSkeleton.js
new file mode 100644
index 0000000..ea1b0f9
--- /dev/null
+++ b/mobile/components/skeletons/GroupCardSkeleton.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Card } from 'react-native-paper';
+import Skeleton from '../ui/Skeleton';
+
+const GroupCardSkeleton = () => {
+ return (
+
+ }
+ left={(props) => (
+
+ )}
+ />
+
+
+
+
+ );
+};
+
+export default GroupCardSkeleton;
diff --git a/mobile/components/ui/Skeleton.js b/mobile/components/ui/Skeleton.js
new file mode 100644
index 0000000..01f6ae2
--- /dev/null
+++ b/mobile/components/ui/Skeleton.js
@@ -0,0 +1,44 @@
+import React, { useEffect, useRef } from 'react';
+import { Animated } from 'react-native';
+import { useTheme } from 'react-native-paper';
+
+const Skeleton = ({ width, height, borderRadius = 4, style }) => {
+ const theme = useTheme();
+ const opacity = useRef(new Animated.Value(0.3)).current;
+
+ useEffect(() => {
+ const pulse = Animated.loop(
+ Animated.sequence([
+ Animated.timing(opacity, {
+ toValue: 0.7,
+ duration: 800,
+ useNativeDriver: true,
+ }),
+ Animated.timing(opacity, {
+ toValue: 0.3,
+ duration: 800,
+ useNativeDriver: true,
+ }),
+ ])
+ );
+ pulse.start();
+ return () => pulse.stop();
+ }, [opacity]);
+
+ return (
+
+ );
+};
+
+export default Skeleton;
diff --git a/mobile/screens/HomeScreen.js b/mobile/screens/HomeScreen.js
index d2f3c38..a37a4e6 100644
--- a/mobile/screens/HomeScreen.js
+++ b/mobile/screens/HomeScreen.js
@@ -1,7 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { Alert, FlatList, RefreshControl, StyleSheet, View } from "react-native";
import {
- ActivityIndicator,
Appbar,
Avatar,
Modal,
@@ -12,6 +11,7 @@ import {
} from "react-native-paper";
import HapticButton from '../components/ui/HapticButton';
import HapticCard from '../components/ui/HapticCard';
+import GroupCardSkeleton from '../components/skeletons/GroupCardSkeleton';
import { HapticAppbarAction } from '../components/ui/HapticAppbar';
import * as Haptics from "expo-haptics";
import { createGroup, getGroups, getOptimizedSettlements } from "../api/groups";
@@ -257,8 +257,14 @@ const HomeScreen = ({ navigation }) => {
{isLoading ? (
-
-
+
+ {[1, 2, 3, 4, 5].map((key) => (
+
+ ))}
) : (