Back to Hip Hip Backend (from
Frontend (from
(Blueprint target: 160)
Documentation
DELIVERABLES
DELIVERABLES — Hip Hip Groups & Sharing (P029)
Summary
Full-stack implementation of group-based list sharing for the Hip Hip todo app. Extends P028 backend (Go + PostgreSQL) and frontend (React + TypeScript) with group CRUD, invitation flow, membership management, list sharing, role-based access control, and dataloaders for N+1 prevention.
Lines of code: ~23,205 total (16,491 Go + 6,714 TypeScript) Files: 102 (41 Go + 61 TypeScript)
Test Results
Backend (from go test ./...)
shell
ok github.com/jonathanleahy/hip-hip/e2e_test
ok github.com/jonathanleahy/hip-hip/internal/adapters/graph
ok github.com/jonathanleahy/hip-hip/internal/app
ok github.com/jonathanleahy/hip-hip/internal/domain
Total Go tests: 200 passing
- Domain validation: 46 tests
- App services (with mocks): 86 tests
- GraphQL resolvers: 8 tests
- E2E (with mock repos): 60 tests
Frontend (from npx vitest run)
shell
Test Files 20 passed (20)
Tests 190 passed (190)
Combined Total
| Category | Count |
|---|---|
| Go domain tests | 46 |
| Go service tests | 86 |
| Go resolver tests | 8 |
| Go E2E tests | 60 |
| Frontend component tests | 93 |
| Frontend page tests | 52 |
| Frontend QA E2E tests | 45 |
| Total | 390 |
Lint Results
shell
Go vet: PASS (no issues)
Go build: PASS
TypeScript: PASS (no type errors)
Full output in docs/lint-output.txt.
Post-Development Gate Verification
- [x]
go vet ./...— clean, no warnings - [x]
go build ./...— compiles without errors - [x] go.mod version (1.25) matches Dockerfile builder image (golang:1.25-alpine)
- [x]
go test ./...— 200 tests passing - [x]
npx tsc --noEmit— no type errors - [x]
npx vitest run— 190 tests passing - [x]
.gitignorefiles created (root, backend, frontend) - [x] Frontend component visual checklist: GroupCard renders color border, MemberAvatar renders colored circle with initials, role badges use solid colors (teal/amber/stone), InviteModal shows link state with copy button, MemberList compact mode renders avatar row with overflow indicator
Features Implemented
Backend (Go)
| Feature | Status |
|---|---|
| FR-1: Group Domain Model & Validation | Complete |
| FR-2: Database Migrations (000005-000009) | Complete |
| FR-3: Group CRUD Operations | Complete |
| FR-4: Invitation Flow | Complete |
| FR-5: Membership Management | Complete |
| FR-6: List Sharing & Access Control | Complete |
| FR-7: Role-Based Authorization | Complete |
| FR-8: Dataloaders (MembersByGroupID, ListsByGroupID) | Complete |
| FR-9: GraphQL Schema & Resolvers | Complete |
Frontend (React + TypeScript)
| Feature | Status |
|---|---|
| FR-10: GroupCard Component | Complete |
| FR-11: MemberList Component (full + compact mode) | Complete |
| FR-12: InviteModal Component (with link state) | Complete |
| FR-13: GroupsPage | Complete |
| FR-14: GroupViewPage | Complete |
| FR-15: GroupSettingsPage (with icon/color fields, non-admin redirect) | Complete |
| FR-16: InvitationPage (with expired/accepted/declined states) | Complete |
| FR-17: Dashboard Update (groups section) | Complete |
| FR-18: Permission-Based UI (read-only TodoItem, hidden controls) | Complete |
| FR-19: Login Redirect Support (with open redirect prevention) | Complete |
| FR-20: .gitignore | Complete |
Nice-to-Have Features
All features are must-have per the spec. No nice-to-have features were defined.
Deferred
None. All 20 functional requirements fully implemented.
Review Iteration 3 Changes
The following fixes were applied in response to review consensus (iteration 2 → 3):
Backend Authorization (FIX-001, FIX-002, FIX-003)
- FIX-001 (Critical): Fixed viewer write bypass in
todo_service.go—verifyListAccessnow checksm.Role == "admin" || m.Role == "member"instead oflist.Permission == "write". Added 3 tests. - FIX-002 (Major): Added group-aware access control to
UpdateListinlist_service.go— non-creator, non-admin members/viewers now receiveErrAdminRequired. Added 2 tests. - FIX-003 (Major): Updated
DeleteListinlist_service.goto allow group admins (not just creator) to delete shared lists. Added 2 tests.
Frontend Features (FIX-004 through FIX-015)
- FIX-004/005: Login/Register redirect support with open redirect prevention (relative-path-only validation).
- FIX-006: Invitation page URL-encodes redirect path via
encodeURIComponent. - FIX-007: Dashboard shows "My Groups" section with GroupCards and "View all" link.
- FIX-008: Permission-based UI in ListDetailPage — viewers see read-only items (no drag handles, disabled checkboxes, no click-to-detail, hidden AddTodo/edit/delete).
- FIX-009: InviteModal transitions to link state after successful invite — shows copyable URL, "Copy Link" button, "Invite Another" reset.
- FIX-010: MemberList compact mode — horizontal avatar row, max 5 visible, "+N" overflow indicator.
- FIX-011: GroupSettingsPage — added icon/color form fields, non-admin redirect via useEffect.
- FIX-013: InvitationPage — separate expired/accepted/declined states, HippoMascot moods, decline navigates to
/. - FIX-014: MemberList role badge colors (admin=teal-600, member=amber-600, viewer=stone-400), remove member confirmation dialog.
- FIX-015: GroupsPage empty state mood "waving", GroupViewPage settings gear admin-only, empty lists HippoMascot.
FR Criterion Coverage
| FR | Criterion | Test |
|---|---|---|
| FR-1 | AC-1.1: Name required | group_test.go: TestGroup_Validate_EmptyName |
| FR-1 | AC-1.2: Name trimmed | group_test.go: TestGroup_Validate_TrimsName |
| FR-1 | AC-1.3: Name max 100 chars | group_test.go: TestGroup_Validate_NameTooLong |
| FR-1 | AC-1.4: Desc max 500 chars | group_test.go: TestGroup_Validate_DescriptionTooLong |
| FR-1 | AC-1.5: Color hex format | group_test.go: TestGroup_Validate_InvalidColors |
| FR-1 | AC-1.6: Icon max 50 chars | group_test.go: TestGroup_Validate_IconTooLong |
| FR-1 | AC-1.7: Default icon "home" | group_test.go: TestGroup_Validate_ValidDefaults |
| FR-1 | AC-1.8: Default color "#0D9488" | group_test.go: TestGroup_Validate_ValidDefaults |
| FR-1 | AC-1.9: Token 43 chars base64url | group_test.go: TestGenerateInvitationToken |
| FR-1 | AC-1.10: Email validation | group_test.go: TestValidateInvitationEmail_Valid / _Invalid |
| FR-1 | AC-1.11: 7-day expiry | group_service_test.go: TestInviteMember_AdminCreatesInvitation |
| FR-1 | AC-1.12: Role validation | group_test.go: TestValidateRole_Valid / _Invalid |
| FR-2 | AC-2.1-2.8: Migrations | Verified via go build + migration embed.go |
| FR-3 | AC-3.1: Auto-admin | group_service_test.go: TestCreateGroup_WithAutoAdmin |
| FR-3 | AC-3.3: GetMyGroups ordered | group_service_test.go: TestGetMyGroups_Ordered |
| FR-3 | AC-3.4: GetGroup member access | group_service_test.go: TestGetGroup_Member / _NonMember |
| FR-3 | AC-3.5: UpdateGroup admin only | group_service_test.go: TestUpdateGroup_Admin / _MemberDenied |
| FR-3 | AC-3.6: Partial update | group_service_test.go: TestUpdateGroup_PartialUpdate |
| FR-3 | AC-3.7: DeleteGroup admin only | group_service_test.go: TestDeleteGroup_Admin / _MemberDenied |
| FR-4 | AC-4.1: InviteMember admin only | group_service_test.go: TestInviteMember_AdminCreatesInvitation / _NonAdminDenied |
| FR-4 | AC-4.3: Self-invite denied | group_service_test.go: TestInviteMember_SelfInvitationDenied |
| FR-4 | AC-4.4: Pending exists | group_service_test.go: TestInviteMember_PendingInvitationExists |
| FR-4 | AC-4.5: Already member | group_service_test.go: TestInviteMember_AlreadyAMember |
| FR-4 | AC-4.6: AcceptInvitation | group_service_test.go: TestAcceptInvitation_Valid |
| FR-4 | AC-4.7: Expired | group_service_test.go: TestAcceptInvitation_Expired |
| FR-4 | AC-4.8: Already accepted/declined | group_service_test.go: TestAcceptInvitation_AlreadyAccepted / _AlreadyDeclined |
| FR-4 | AC-4.9: Not found | group_service_test.go: TestAcceptInvitation_NotFound |
| FR-4 | AC-4.10: Already member on accept | group_service_test.go: TestAcceptInvitation_AlreadyAMember |
| FR-4 | AC-4.11: DeclineInvitation | group_service_test.go: TestDeclineInvitation |
| FR-4 | AC-4.12: GetInvitationInfo | group_service_test.go: TestGetInvitationInfo_Valid / _NotFound |
| FR-5 | AC-5.1: Admin removes member | group_service_test.go: TestRemoveMember_AdminRemovesMember |
| FR-5 | AC-5.2: Self-removal | group_service_test.go: TestRemoveMember_SelfRemoval |
| FR-5 | AC-5.3: Last admin | group_service_test.go: TestRemoveMember_LastAdmin |
| FR-5 | AC-5.4: Non-member | group_service_test.go: TestRemoveMember_NonMember |
| FR-5 | AC-5.5: UpdateMemberRole | group_service_test.go: TestUpdateMemberRole_Admin |
| FR-5 | AC-5.6: Creator immutable | group_service_test.go: TestUpdateMemberRole_CannotChangeCreator |
| FR-5 | AC-5.7: Last admin demotion | group_service_test.go: TestUpdateMemberRole_LastAdminDemotion |
| FR-5 | AC-5.8: Invalid role | group_service_test.go: TestUpdateMemberRole_InvalidRole |
| FR-6 | AC-6.1: ShareList owner only | group_service_test.go: TestShareList_OwnerShares |
| FR-6 | AC-6.2: Viewer denied | group_service_test.go: TestShareList_ViewerDenied |
| FR-6 | AC-6.3: Already shared | group_service_test.go: TestShareList_AlreadyShared |
| FR-6 | AC-6.5-6.8: Permission checks | group_service_test.go: TestGetUserPermission_* (4 tests) |
| FR-7 | AC-7.1-7.3: Auth matrix | todo_service_test.go: TestCreateItem_GroupMember_Allowed / _GroupViewer_Denied / _GroupAdmin_Allowed; list_service_test.go: TestUpdateList_SharedList_* / TestDeleteList_SharedList_* |
| FR-8 | AC-8.1-8.6: Dataloaders | e2e_test.go: batch loading via GetMyGroups + GetGroup |
| FR-9 | AC-9.1-9.10: Schema & resolvers | resolvers_test.go: 8 resolver tests |
| FR-10 | AC-10.1-10.7: GroupCard | GroupCard.test.tsx: 5 tests (name, counts, singular, click, border) |
| FR-11 | AC-11.1-11.8: MemberList | MemberList.test.tsx: 12 tests (render, badges, admin controls, callbacks, compact mode, confirmation) |
| FR-12 | AC-12.1-12.8: InviteModal | InviteModal.test.tsx: 7 tests (render, validation, link state, copy, invite another, error, close) |
| FR-13 | AC-13.1-13.7: GroupsPage | GroupsPage.test.tsx: 5 tests (loading, empty, cards, error, create) |
| FR-14 | AC-14.1-14.9: GroupViewPage | GroupViewPage.test.tsx: 10 tests (name, members, lists, settings, invite, empty) |
| FR-15 | AC-15.1-15.6: GroupSettingsPage | GroupSettingsPage.test.tsx: 6 tests (form, validation, delete, loading, error) |
| FR-16 | AC-16.1-16.9: InvitationPage | InvitationPage.test.tsx: 8 tests (token, loading, unauth, auth, accept, decline, expired, error) |
| FR-17 | AC-17.1-17.5: Dashboard | DashboardPage.test.tsx: 8 tests (welcome, lists, empty, create, groups) |
| FR-18 | AC-18.1-18.5: Permission UI | ListDetailPage.test.tsx: read-only rendering; TodoItem.test.tsx: readOnly prop tests |
| FR-19 | AC-19.1-19.3: Login redirect | LoginPage.tsx / RegisterPage.tsx: getRedirectPath() with open redirect prevention; InvitationPage.test.tsx: redirect links |
| FR-20 | AC-20.1-20.3: .gitignore | 3 files created (root, backend, frontend) |
Known Issues
None. All tests pass, all lint checks clean.
Database Migrations
| Migration | Description |
|---|---|
| 000005_create_groups | Groups table with name, description, icon, color |
| 000006_create_group_memberships | Composite PK (user_id, group_id), role enum |
| 000007_create_invitations | Token-based invitations with status tracking |
| 000008_add_group_id_fk | FK on todo_lists.group_id → groups(id) ON DELETE SET NULL |
| 000009_add_avatar_color | avatar_color column on users table |