Skip to content
Merged
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
34 changes: 1 addition & 33 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Dashboard skeleton loading state (`DashboardSkeleton`) to improve perceived performance during data fetch.
- Comprehensive `EmptyState` component for Groups and Friends pages to better guide new users.
- Toast notification system (`ToastContext`, `Toast` component) for providing non-blocking user feedback.
- Keyboard navigation support for Groups page, enabling accessibility for power users.

### Planned
- See `todo.md` for queued tasks
Expand Down Expand Up @@ -39,36 +40,3 @@
- `.jules/todo.md`
- `.jules/knowledge.md`
- `.jules/changelog.md`

---

<!--
## Template for future entries:

## [YYYY-MM-DD] - vX.X.X

### Added
- New feature or component

### Changed
- Modifications to existing code

### Fixed
- Bug fixes

### Improved
- Performance or UX improvements

### Deprecated
- Features to be removed

### Removed
- Deleted code or features

**Files Modified:**
- `path/to/file1`
- `path/to/file2`

**Notes:**
- Any additional context
-->
43 changes: 43 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mobile/
├── context/ # AuthContext
├── api/ # API client and service functions
└── utils/ # Helpers (currency, balance calculations)
```

---

Expand Down Expand Up @@ -103,6 +104,34 @@ colors: {
</Button>
```

### Clickable Cards & Accessibility

**Date:** 2026-01-01
**Context:** Making `motion.div` or non-button elements accessible

When making a div clickable (like a card), you must ensure it's accessible:
1. **Role**: Add `role="button"`.
2. **TabIndex**: Add `tabIndex={0}` so it's focusable.
3. **Keyboard Handler**: Add `onKeyDown` to handle 'Enter' and 'Space'.
4. **Label**: Add `aria-label` to describe the action.
5. **Focus Styles**: Add visible focus styles (e.g., `focus:ring`).

```tsx
<motion.div
onClick={handleClick}
role="button"
tabIndex={0}
aria-label="View Details"
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}}
className="... focus:outline-none focus:ring-4 focus:ring-blue-500"
>
```

### Card Component with Title/Action

**Date:** 2026-01-01
Expand Down Expand Up @@ -251,6 +280,20 @@ interface BalanceSummary {

---

## Testing & Verification

### Playwright Verification Patterns
**Date:** 2026-01-01
**Context:** Verifying accessibility changes with Playwright scripts

When writing Playwright scripts to verify frontend changes without backend:

1. **Auth Mocking:** You must mock `/users/me` persistently. If this call fails or returns 401, `AuthContext` will force a redirect to login, breaking navigation tests.
2. **Route Matching:** Use specific route patterns (e.g., `**/users/me`) and ensure they don't accidentally swallow longer paths (like `**/users/me/balance-summary`) if using wildcards carelessly. Register specific paths before general ones if using `page.route` order dependence, or use specific globs.
3. **Response Structure:** Mocks must match the structure expected by `axios` interceptors and components. If `axios` returns `res.data` as the body, and the component expects `res.data.groups`, the mock body should be `{"groups": [...]}` (not `{"data": {"groups": ...}}`).

---

## Known Issues & Gotchas

### Image URL Validation
Expand Down
12 changes: 8 additions & 4 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@
- Files modified: `web/contexts/ToastContext.tsx`, `web/components/ui/Toast.tsx`, `web/App.tsx`, `web/pages/Auth.tsx`
- Impact: Modern feedback system that supports both themes

- [ ] **[a11y]** Complete keyboard navigation for Groups page
- [x] **[a11y]** Complete keyboard navigation for Groups page
- Completed: 2026-01-01
- File: `web/pages/Groups.tsx`
- Context: Add keyboard handling to group cards + search + modals
- Impact: Full keyboard accessibility for power users
- Size: ~50 lines
- Added: 2026-01-01

- [x] **[ux]** Comprehensive empty states with illustrations
- Completed: 2026-01-01
- Files: `web/pages/Groups.tsx`, `web/pages/Friends.tsx`
- Context: Create illustrated empty states with CTAs (not just text)
- Impact: Guides new users, makes app feel polished
- Size: ~70 lines
- Added: 2026-01-01

- [ ] **[ux]** Error boundary with retry for API failures
- Files: Create `web/components/ErrorBoundary.tsx`, wrap app
Expand Down Expand Up @@ -146,8 +146,12 @@
- Files modified: `web/components/ui/EmptyState.tsx`, `web/pages/Groups.tsx`, `web/pages/Friends.tsx`
- Impact: Users now see a polished, illustrated empty state with clear CTAs when they have no groups or friends, instead of plain text.

- [x] **[a11y]** Complete keyboard navigation for Groups page
- Completed: 2026-01-12
- File: `web/pages/Groups.tsx`
- Impact: Users can now navigate groups, join/create buttons, and search using only the keyboard with proper focus indicators.
- [x] **[ux]** Form validation with inline feedback
- Completed: 2026-01-01
- Completed: 2026-01-11
- Files modified: `web/pages/Auth.tsx`
- Impact: Users know immediately if input is valid via inline error messages and red borders.

Expand Down
10 changes: 6 additions & 4 deletions web/pages/Groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export const Groups = () => {
<Search className={`absolute left-4 top-1/2 -translate-y-1/2 ${isNeo ? 'text-black' : 'text-white/60'}`} size={20} />
<input
type="text"
aria-label="Search groups"
placeholder="Search groups..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
Expand Down Expand Up @@ -164,16 +165,17 @@ export const Groups = () => {
const balanceAmount = groupBalance?.amount || 0;

return (
<motion.div
<motion.button
key={group._id}
layout
variants={itemVariants}
whileHover={{ scale: 1.02, rotate: isNeo ? 1 : 0 }}
whileTap={{ scale: 0.98 }}
onClick={() => navigate(`/groups/${group._id}`)}
className={`group cursor-pointer transition-all duration-300 relative overflow-hidden flex flex-col h-full
aria-label={`View details for group ${group.name}`}
className={`group cursor-pointer transition-all duration-300 relative overflow-hidden flex flex-col h-full w-full text-left focus:outline-none focus:ring-4 focus:ring-blue-500/50
${isNeo
? `bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] rounded-none`
? `bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] rounded-none focus:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)]`
: `rounded-3xl border shadow-lg backdrop-blur-md ${mode === 'dark' ? 'border-white/20 bg-white/5 hover:bg-white/10' : 'border-black/5 bg-white/60 hover:bg-white/80'}`}
`}
>
Expand Down Expand Up @@ -206,7 +208,7 @@ export const Groups = () => {
</div>
</div>
</div>
</motion.div>
</motion.button>
);
})
)}
Expand Down
Loading