Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@
- `.jules/todo.md`
- `.jules/knowledge.md`
- `.jules/changelog.md`

### Mobile
- Added reusable animated `Skeleton` primitive component in `mobile/components/ui/Skeleton.js`.
Comment on lines +127 to +128
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

markdownlint MD022: add a blank line after the ### Mobile heading.

📝 Proposed fix
 ### Mobile
+
 - Added reusable animated `Skeleton` primitive component in `mobile/components/ui/Skeleton.js`.
📝 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.

Suggested change
### Mobile
- Added reusable animated `Skeleton` primitive component in `mobile/components/ui/Skeleton.js`.
### Mobile
- Added reusable animated `Skeleton` primitive component in `mobile/components/ui/Skeleton.js`.
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 127-127: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.Jules/changelog.md around lines 127 - 128, The changelog's "### Mobile"
heading lacks a trailing blank line causing markdownlint MD022; edit
.Jules/changelog.md and insert a single blank line immediately after the "###
Mobile" heading (i.e., ensure there is an empty line between the heading "###
Mobile" and the following list item "- Added reusable animated `Skeleton`
primitive component...") so the heading is separated from the content.

- Created `HomeScreenSkeleton` loading view for groups list.
- Replaced basic `ActivityIndicator` in `HomeScreen.js` with new skeleton loading experience.
3 changes: 3 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -756,3 +756,6 @@ _Document errors and their solutions here as you encounter them._
- react-native-paper: UI components
- axios: API calls (via api/client.js)
- expo: Platform SDK
- When using `Animated.loop` within a `useEffect` hook in React Native components, assign the loop to a variable and explicitly call `.stop()` on it in the cleanup function to prevent memory leaks.
- Playwright in the testing environment cannot be run with `headless=False`; attempting to do so will result in an 'Executable doesn't support UI mode' error.
- Always clean up temporary Playwright verification scripts, logs, and generated screenshots before committing changes to maintain repository hygiene.
5 changes: 3 additions & 2 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
- 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-14
- Files modified: `mobile/screens/HomeScreen.js`, `mobile/components/skeletons/HomeScreenSkeleton.js`, `mobile/components/ui/Skeleton.js`
- Context: Replace ActivityIndicator with skeleton group cards
- Impact: Better loading experience, less jarring
- Size: ~40 lines
Expand Down
36 changes: 36 additions & 0 deletions mobile/components/skeletons/HomeScreenSkeleton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Card } from 'react-native-paper';
import Skeleton from '../ui/Skeleton';

const HomeScreenSkeleton = () => {
return (
<View style={styles.container} accessibilityRole="progressbar" accessibilityLabel="Loading groups">
{Array.from({ length: 5 }).map((_, i) => (
<Card key={i} style={styles.card}>
<Card.Title
title={<Skeleton width={120} height={18} borderRadius={4} />}
left={() => <Skeleton width={40} height={40} borderRadius={20} />}
/>
Comment on lines +10 to +14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

react-native-paper 5.x Card.Title title prop type accepted values

💡 Result:

The title prop of Card.Title in react-native-paper 5.x accepts React.ReactNode values. This includes strings, numbers, JSX elements (like components), and other renderable React children. The TypeScript definition explicitly states: "Text for the title. Note that this will only accept a string or -based node," confirming compatibility with text-based React nodes.

Citations:


🏁 Script executed:

# First, check if the file exists and verify the code snippet
cat -n mobile/components/skeletons/HomeScreenSkeleton.js | head -20

Repository: Devasy/splitwiser

Length of output: 975


🏁 Script executed:

# Find react-native-paper package.json to identify version
find . -name "package.json" -type f | head -5 | xargs grep -l "react-native-paper" 2>/dev/null

Repository: Devasy/splitwiser

Length of output: 82


🏁 Script executed:

# Check for Skeleton component definition in the repo
fd -t f "Skeleton" --extension js --extension ts --extension tsx mobile/

Repository: Devasy/splitwiser

Length of output: 143


🏁 Script executed:

# Look for HomeScreen.js file to check the pattern at line 196
fd -t f "HomeScreen.js"

Repository: Devasy/splitwiser

Length of output: 89


🏁 Script executed:

# Check the react-native-paper version
cat mobile/package.json | grep -A 2 -B 2 "react-native-paper"

Repository: Devasy/splitwiser

Length of output: 241


🏁 Script executed:

# Examine the Skeleton component definition
cat mobile/components/ui/Skeleton.js

Repository: Devasy/splitwiser

Length of output: 1096


🏁 Script executed:

# Check HomeScreen.js line 196 and surrounding context
sed -n '190,205p' mobile/screens/HomeScreen.js

Repository: Devasy/splitwiser

Length of output: 646


🏁 Script executed:

# Check if node_modules has react-native-paper TypeScript definitions
find node_modules -path "*/react-native-paper/*/CardTitle.d.ts" 2>/dev/null | head -1 | xargs cat

Repository: Devasy/splitwiser

Length of output: 43


🏁 Script executed:

# Alternative: search for CardTitle types in node_modules
find node_modules/react-native-paper -name "*Card*" -type f | grep -E "\.(d\.ts|ts)$" | head -5

Repository: Devasy/splitwiser

Length of output: 127


🏁 Script executed:

# Let's also check what HapticCard is in the codebase (different from Card.Title pattern)
rg -t ts -t tsx "HapticCard" mobile/ -B 2 -A 2 | head -30

Repository: Devasy/splitwiser

Length of output: 88


🏁 Script executed:

# Let's search for how Card.Title is used elsewhere in the codebase
rg "Card\.Title" mobile/ -B 2 -A 4 -t js

Repository: Devasy/splitwiser

Length of output: 4271


🏁 Script executed:

# Check if there's a type definition file in the project itself
find . -name "*.d.ts" -type f | head -10

Repository: Devasy/splitwiser

Length of output: 43


🏁 Script executed:

# Verify if there are any runtime issues or patterns in similar React Native projects
# Let's also check if the title prop is documented to accept render functions
rg -t js "title=\{.*=>.*\}" mobile/ | head -10

Repository: Devasy/splitwiser

Length of output: 43


🏁 Script executed:

# Double-check if there's any other use of JSX elements as Card.Title props
rg "Card\.Title" mobile/ -A 2 | grep -E "title=\{<"

Repository: Devasy/splitwiser

Length of output: 184


title prop receives a React element — use the render-function form instead.

react-native-paper Card.Title's title prop accepts string | ((props) => React.ReactNode) and internally wraps the string value in a <Text numberOfLines> component. Passing a non-text component like <Skeleton /> (an Animated.View) directly as title does not align with the prop's type signature and can cause layout issues in React Native. Use the render-function form, consistent with the left prop on line 13 and standard usage throughout the codebase.

🔧 Proposed fix
           <Card.Title
-            title={<Skeleton width={120} height={18} borderRadius={4} />}
+            title={() => <Skeleton width={120} height={18} borderRadius={4} />}
             left={() => <Skeleton width={40} height={40} borderRadius={20} />}
           />
📝 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.

Suggested change
<Card key={i} style={styles.card}>
<Card.Title
title={<Skeleton width={120} height={18} borderRadius={4} />}
left={() => <Skeleton width={40} height={40} borderRadius={20} />}
/>
<Card key={i} style={styles.card}>
<Card.Title
title={() => <Skeleton width={120} height={18} borderRadius={4} />}
left={() => <Skeleton width={40} height={40} borderRadius={20} />}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mobile/components/skeletons/HomeScreenSkeleton.js` around lines 10 - 14,
Card.Title currently receives a React element for its title prop
(title={<Skeleton ... />}), which mismatches the prop signature; change it to
the render-function form like left uses (e.g., title={() => <Skeleton
width={120} height={18} borderRadius={4} />}) so Card.Title renders a node
correctly; update the Card.Title invocation in HomeScreenSkeleton.js to pass a
function for title that returns the Skeleton component.

<Card.Content>
<Skeleton width={180} height={14} borderRadius={4} style={styles.contentSkeleton} />
</Card.Content>
</Card>
))}
</View>
);
};

const styles = StyleSheet.create({
container: {
padding: 16,
},
card: {
marginBottom: 16,
},
contentSkeleton: {
marginTop: 4,
},
});

export default HomeScreenSkeleton;
45 changes: 45 additions & 0 deletions mobile/components/ui/Skeleton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useEffect, useRef } from 'react';
import { Animated, View, StyleSheet } from 'react-native';
import { useTheme } from 'react-native-paper';

const Skeleton = ({ width, height, borderRadius = 4, style }) => {
const theme = useTheme();
const opacityAnim = useRef(new Animated.Value(0.3)).current;

useEffect(() => {
const loop = Animated.loop(
Animated.sequence([
Animated.timing(opacityAnim, {
toValue: 1,
duration: 700,
useNativeDriver: true,
}),
Animated.timing(opacityAnim, {
toValue: 0.3,
duration: 700,
useNativeDriver: true,
}),
])
);
loop.start();

return () => loop.stop();
}, [opacityAnim]);

return (
<Animated.View
style={[
{
width,
height,
borderRadius,
backgroundColor: theme.colors.surfaceVariant || '#e0e0e0',
opacity: opacityAnim,
},
style,
]}
/>
);
};

export default Skeleton;
5 changes: 2 additions & 3 deletions mobile/screens/HomeScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as Haptics from "expo-haptics";
import { createGroup, getGroups, getOptimizedSettlements } from "../api/groups";
import { AuthContext } from "../context/AuthContext";
import { formatCurrency, getCurrencySymbol } from "../utils/currency";
import HomeScreenSkeleton from "../components/skeletons/HomeScreenSkeleton";

const HomeScreen = ({ navigation }) => {
const { token, logout, user } = useContext(AuthContext);
Expand Down Expand Up @@ -257,9 +258,7 @@ const HomeScreen = ({ navigation }) => {
</Appbar.Header>

{isLoading ? (
<View style={styles.loaderContainer}>
<ActivityIndicator size="large" />
</View>
<HomeScreenSkeleton />
) : (
<FlatList
data={groups}
Expand Down
Loading