Story 2.8.4: Partner Contact Management
Status
Story
As an organizer, I want to manage partner contacts through the Contacts tab with add/remove functionality and role assignment, so that I can maintain accurate contact information for each partner company with designated primary, billing, technical, and marketing contacts.
Domain Context
Primary Domain
Partner Analytics Domain - Partner contact management for organizer workflows
Involved Services
- Frontend: React 19 application (TypeScript, Material-UI v5, Zustand, React Query)
- Backend: Partner Coordination Service (REST APIs from Story 2.7)
- Backend: User Management Service (for user autocomplete and data enrichment via HTTP)
- Cache: React Query for client-side caching (10min TTL for contacts)
Shared-Kernel Foundation (automatically included):
- ErrorResponse (standardized error format with correlationId)
- HTTP client utilities (JWT propagation, error handling)
OpenAPI Code Generation:
- Generate TypeScript types from
docs/api/partners-api.openapi.yml
Cross-Domain Dependencies
- Depends on: Story 2.8.2 (Partner Detail View) for Contacts tab container
- Depends on: Story 1.17 (React Frontend Foundation) for shared components
- Depends on: Story 2.7 (Partner Coordination Service Foundation) for Partner Contact APIs
- Depends on: Story 2.1b (User Management Service) for user autocomplete and enrichment
- Integrates with: API Gateway for authentication
Requirements Context
Related Functional Requirements
- FR8: Partner strategic input (contacts manage topic voting and suggestions)
- FR12: Partner meeting coordination (contacts receive meeting invitations)
- NFR-Performance: Contact list load <1s, add/remove operations <500ms
- NFR-UX: WCAG 2.1 AA accessibility, inline validation
Acceptance Criteria Source
Epic 2, Story 2.8.4 (derived from wireframe docs/wireframes/story-6.3-partner-detail.md Contacts Tab)
Architecture Context
Architecture Patterns
[Source: architecture/05-frontend-architecture.md]
- State Management: React Query for server state (contacts list, mutations)
- Form Management: React Hook Form with Zod validation
- Validation: Zod schemas with business rule validation (primary contact required)
- API Path Pattern: Use
/partners/{companyName}/contacts(WITHOUT/api/v1prefix)
[Source: architecture/04-api-design.md#partners-api]
- List Contacts: GET /partners/{companyName}/contacts (returns enriched user data)
- Add Contact: POST /partners/{companyName}/contacts with AddPartnerContactRequest
- Remove Contact: DELETE /partners/{companyName}/contacts/{username}
- HTTP Enrichment (ADR-004): Backend calls User Service to enrich contact data
- Validation: Backend enforces at least one PRIMARY contact
[Source: architecture/03-data-architecture.md#partner-contact]
- PartnerContact Entity: id (UUID), partnerId, username (meaningful ID - ADR-003), contactRole, isPrimary
- Contact Roles: PRIMARY, BILLING, TECHNICAL, MARKETING
- Business Rule: At least one PRIMARY contact required per partner
- UNIQUE Constraint: (partnerId, username) - prevent duplicate contacts
Database Schema Reference
N/A - Frontend story, no database migrations required. Backend schema handled in Story 2.7.
Infrastructure Components
- Frontend: Contacts tab integrated into Partner Detail view
- CDN: User profile pictures from CloudFront
Wireframe Context
Wireframe References
Primary Wireframe: docs/wireframes/story-6.3-partner-detail.md (Contacts Tab)
Contacts Tab Implemented:
-
Contacts Tab in Partner Detail
- Contact list with user data (name, email, role, primary indicator)
- [+ Add Contact] button
- Contact cards with edit/remove actions
- Primary contact badge (β)
- Role badges (PRIMARY, BILLING, TECHNICAL, MARKETING)
- User profile pictures (from CloudFront)
-
Add Contact Modal
- User autocomplete search (by username or email)
- Contact role dropdown (PRIMARY, BILLING, TECHNICAL, MARKETING)
- Primary contact toggle (only one primary allowed)
- Form actions: [Cancel] [Add]
-
Edit Contact Modal
- User display (read-only)
- Contact role dropdown (editable)
- Primary contact toggle (editable, with validation)
- Form actions: [Cancel] [Save]
Navigation Flows:
- From Partner Detail β Click Contacts tab β Display contacts list
- From Contacts tab β Click [+ Add Contact] β Add Contact Modal
- From Contact card β Click [Edit] β Edit Contact Modal
- From Contact card β Click [Remove] β Confirmation dialog β Remove contact
UI Components
Feature Components:
PartnerContactsTab.tsx- Contacts tab container (integrated in Story 2.8.2)ContactList.tsx- List of contacts with cardsContactCard.tsx- Individual contact card with user data, role badge, actionsAddContactModal.tsx- Add new contact modalEditContactModal.tsx- Edit existing contact modalUserAutocomplete.tsx- User search/selection (reuse from Story 2.5.2 if exists)ContactRoleSelect.tsx- Role dropdownPrimaryContactToggle.tsx- Primary toggle with validation
Shared Components:
Modal(dialog wrapper)ConfirmDialog(remove confirmation)FormField,SelectField(form inputs)Avatar(user profile pictures)
Material-UI Components:
- Card, CardContent, CardActions
- Autocomplete (user selection)
- Select, MenuItem (role selection)
- Switch (primary toggle)
- Chip (role badges)
- Badge (primary indicator β)
- Button, IconButton
- Dialog, DialogTitle, DialogContent, DialogActions
State Management:
-
Zustand Store:
useContactModalStoreisAddModalOpen: booleanisEditModalOpen: booleancontactToEdit: PartnerContactResponse | nullopenAddModal(),openEditModal(contact),closeModals()
-
React Query:
usePartnerContacts(companyName)- List with enrichment, cache 10minuseAddPartnerContact()- Mutation for adduseRemovePartnerContact()- Mutation for removeuseUpdatePartnerContact()- Mutation for edit (Epic 8 feature if not in Story 2.7)- Query keys:
['partner', companyName, 'contacts']
Acceptance Criteria
-
Contacts Tab
- Integrated into Partner Detail view (Story 2.8.2)
- Tab label: βContactsβ with count badge (e.g., βContacts (5)β)
- Contact list with enriched user data (name, email, role, profile picture)
- Primary contact indicator (β star icon/badge)
- Role badges (PRIMARY, BILLING, TECHNICAL, MARKETING with color coding)
- User profile pictures (from CloudFront CDN, fallback to initials avatar)
- [+ Add Contact] button (top right)
- Empty state: βNo contacts yet. Add the first contact.β with [+ Add Contact] button
-
Contact Cards
- User profile picture (avatar)
- Full name (firstName + lastName from enriched user data)
- Email (from enriched user data)
- Contact role (badge with icon)
- π€ PRIMARY (primary color)
- π³ BILLING (blue)
- π§ TECHNICAL (orange)
- π§ MARKETING (green)
- Primary indicator (β for isPrimary=true)
- Actions: [Edit Role] [Remove] buttons
- Tooltip on hover: βPrimary contact for this partnershipβ
-
Add Contact Modal
- Modal title: βAdd Contactβ
- User autocomplete field (search by username or email)
- Min 2 characters to trigger search
- Display: Username, full name, email
- Loading indicator while searching
- Empty state: βNo users foundβ
- Contact role dropdown (PRIMARY, BILLING, TECHNICAL, MARKETING)
- Primary contact toggle (switch)
- Disabled if partner already has primary contact
- Tooltip: βThis partner already has a primary contactβ
- Form actions: [Cancel] [Add]
- Validation:
- User required
- Role required
- Cannot add duplicate contact (same partner + username)
- Error handling: Display API errors (409 for duplicate, 404 for user not found)
-
Add Contact Operation
- Click [Add] β POST /api/v1/partners/{companyName}/contacts
- Request body: username, contactRole, isPrimary
- Success: Close modal, refresh contacts list, show success toast
- Error handling:
- 409 Conflict: βThis user is already a contact for this partnerβ
- 404 Not Found: βUser not foundβ
- 400 Validation: Display validation errors
- Loading state: Disable form, show spinner on [Add] button
- Optimistic update: Add contact to list immediately, revert on error
-
Edit Contact Modal
- Modal title: βEdit Contactβ
- User display (read-only, show username and full name)
- Contact role dropdown (editable)
- Primary contact toggle (editable)
- Cannot unset primary if only primary contact
- Warning: βAt least one primary contact is requiredβ
- Form actions: [Cancel] [Save]
- Validation: Role required, primary validation
- Update operation: PATCH /partners/{companyName}/contacts/{username}
-
Remove Contact
- Click [Remove] β Confirmation dialog
- Title: βRemove Contact?β
- Message: βAre you sure you want to remove [Full Name] ([email]) from this partnership?β
- Actions: [Cancel] [Remove]
- Click [Remove] in dialog β DELETE /api/v1/partners/{companyName}/contacts/{username}
- Success: Refresh contacts list, show success toast
- Error handling:
- 400 Bad Request: βCannot remove last primary contactβ
- 404 Not Found: βContact not foundβ
- Business rule validation: Cannot remove if only primary contact
- Optimistic update: Remove from list immediately, revert on error
- Click [Remove] β Confirmation dialog
-
Primary Contact Validation
- At least one PRIMARY contact required per partner
- Cannot remove last primary contact (API returns 400)
- Cannot unset primary toggle if only primary contact
- When adding new primary contact, automatically unset previous primary (backend handles)
- Visual indicator: Warning message when attempting invalid operation
-
User Autocomplete
- Search users by username or email (debounced 300ms)
- Call User Management Service API: GET /users/search?q={query}
- Display: Username, full name, email, profile picture
- Loading indicator while searching
- Empty state: βNo users found matching β{query}ββ
- Error handling: Display error message if search fails
- Keyboard navigation (Arrow keys, Enter to select)
-
Contact List Loading States
- Initial load: Full-page skeleton (3-5 contact card skeletons)
- Refresh: Show loading indicator (do not hide existing contacts)
- Empty state: βNo contacts yetβ message with illustration
- Error state: Error message with [Retry] button
-
Responsive Design
- Mobile (<640px): Single column, stacked contact cards, fullscreen modals
- Tablet (640-1024px): Two columns contact grid
- Desktop (>1024px): Three columns contact grid
-
Accessibility
- Keyboard navigation (Tab, Enter, Escape)
- Screen reader announcements (contacts added/removed, modal open)
- ARIA labels for all buttons and form fields
- Focus indicators visible
- Color contrast for role badges β₯4.5:1
-
Internationalization
- All UI text translated (buttons, labels, messages)
- Role labels translated (PRIMARY β PrimΓ€r, BILLING β Abrechnung, etc.)
- Confirmation messages translated
-
Integration Tests
- Test list contacts API call with HTTP enrichment
- Test add contact API call
- Test remove contact API call
- Test duplicate contact error (409)
- Test primary contact validation (400)
- Test user autocomplete API call
- Test error scenarios (404, 500, network errors)
Test Specifications (TDD)
Test Scenarios by Acceptance Criteria
AC1 Tests: Contacts Tab
- Test 1.1:
should_renderContactsTab_when_tabActivated - Test 1.2:
should_displayContactsList_when_contactsLoaded - Test 1.3:
should_displayCountBadge_when_contactsExist - Test 1.4:
should_displayEmptyState_when_noContacts - Test 1.5:
should_displayAddButton_when_tabRendered
AC2 Tests: Contact Cards
- Test 2.1:
should_renderContactCard_when_contactProvided - Test 2.2:
should_displayEnrichedUserData_when_contactLoaded - Test 2.3:
should_displayRoleBadge_when_contactHasRole - Test 2.4:
should_displayPrimaryIndicator_when_isPrimaryTrue - Test 2.5:
should_displayProfilePicture_when_userHasPicture - Test 2.6:
should_displayInitialsAvatar_when_noPicture
AC3 Tests: Add Contact Modal
- Test 3.1:
should_openAddModal_when_addButtonClicked - Test 3.2:
should_searchUsers_when_autocompleteInputChanged - Test 3.3:
should_validateRequiredFields_when_addClicked - Test 3.4:
should_disablePrimaryToggle_when_primaryContactExists - Test 3.5:
should_displayTooltip_when_primaryToggleDisabled
AC4 Tests: Add Contact Operation
- Test 4.1:
should_addContact_when_formValid - Test 4.2:
should_closeModal_when_addSucceeds - Test 4.3:
should_refreshContactsList_when_addSucceeds - Test 4.4:
should_showSuccessToast_when_addSucceeds - Test 4.5:
should_displayDuplicateError_when_409Returned - Test 4.6:
should_displayUserNotFoundError_when_404Returned - Test 4.7:
should_optimisticallyUpdate_when_addCalled
AC5 Tests: Edit Contact Modal (if implemented)
- Test 5.1:
should_openEditModal_when_editButtonClicked - Test 5.2:
should_prefillForm_when_editModalOpened - Test 5.3:
should_validatePrimaryToggle_when_onlyPrimaryContact - Test 5.4:
should_displayWarning_when_cannotUnsetPrimary
AC6 Tests: Remove Contact
- Test 6.1:
should_showConfirmation_when_removeClicked - Test 6.2:
should_removeContact_when_confirmClicked - Test 6.3:
should_refreshContactsList_when_removeSucceeds - Test 6.4:
should_showError_when_cannotRemoveLastPrimary - Test 6.5:
should_optimisticallyUpdate_when_removeCalled - Test 6.6:
should_revertOnError_when_removeFails
AC7 Tests: Primary Contact Validation
- Test 7.1:
should_enforcePrimaryRequired_when_validating - Test 7.2:
should_preventRemovingLastPrimary_when_onlyOneExists - Test 7.3:
should_displayWarning_when_violationAttempted - Test 7.4:
should_unsetPreviousPrimary_when_newPrimaryAdded
AC8 Tests: User Autocomplete
- Test 8.1:
should_searchUsers_when_queryChanged - Test 8.2:
should_debounceSearch_when_userTyping - Test 8.3:
should_displayUserOptions_when_searchCompletes - Test 8.4:
should_selectUser_when_optionClicked - Test 8.5:
should_displayEmptyState_when_noResults
AC9 Tests: Loading States
- Test 9.1:
should_displaySkeleton_when_initialLoad - Test 9.2:
should_displayLoadingIndicator_when_refreshing - Test 9.3:
should_displayEmptyState_when_noContacts - Test 9.4:
should_displayErrorState_when_loadFails - Test 9.5:
should_retryLoad_when_retryClicked
AC10 Tests: Responsive Design
- Test 10.1:
should_renderSingleColumn_when_mobileViewport - Test 10.2:
should_renderTwoColumns_when_tabletViewport - Test 10.3:
should_renderThreeColumns_when_desktopViewport
AC11 Tests: Accessibility
- Test 11.1:
should_supportKeyboardNav_when_modalOpen - Test 11.2:
should_announceChanges_when_screenReaderActive - Test 11.3:
should_haveAriaLabels_when_buttonsRendered
AC12 Tests: Internationalization
- Test 12.1:
should_translateLabels_when_localeChanged - Test 12.2:
should_translateRoles_when_localeSet
AC13 Tests: Integration
- Test 13.1:
should_callContactsAPI_when_tabActivated - Test 13.2:
should_includeUserData_when_httpEnrichmentUsed - Test 13.3:
should_callAddContactAPI_when_formSubmitted - Test 13.4:
should_callRemoveContactAPI_when_confirmClicked - Test 13.5:
should_handleDuplicateError_when_409Returned
Test File Locations
Frontend Tests:
web-frontend/src/components/organizer/PartnerManagement/PartnerContactsTab.test.tsxweb-frontend/src/components/organizer/PartnerManagement/ContactList.test.tsxweb-frontend/src/components/organizer/PartnerManagement/ContactCard.test.tsxweb-frontend/src/components/organizer/PartnerManagement/AddContactModal.test.tsxweb-frontend/src/components/organizer/PartnerManagement/EditContactModal.test.tsxweb-frontend/src/components/organizer/PartnerManagement/UserAutocomplete.test.tsxweb-frontend/src/hooks/usePartnerContacts.test.tsweb-frontend/src/hooks/useAddPartnerContact.test.tsweb-frontend/src/hooks/useRemovePartnerContact.test.ts
E2E Tests:
web-frontend/e2e/partner-contact-management.spec.ts
Test Data & Mocks
Test Data Builders:
PartnerContactTestDataBuilderUserSearchResultBuilderAddContactRequestBuilder
Mock Services:
- Mock Partner Contact API: GET/POST/DELETE /partners/{companyName}/contacts
- Mock User Search API: GET /users/search
- Mock User Service HTTP enrichment responses
Tasks / Subtasks (TDD Workflow)
β οΈ CRITICAL: Tests must be written BEFORE implementation code (Red-Green-Refactor).
-
Task 1a: Partner Contact API Integration - RED Phase
- Write failing test:
- Write failing test:
- Write failing test:
- Write failing test:
- Write failing test:
- Write failing test:
-
Task 1b: Partner Contact API Integration - GREEN Phase
- Add to
src/services/api/partnerApi.tsgetPartnerContacts(companyName)usingGET /partners/{companyName}/contactsaddPartnerContact(companyName, request)usingPOST /partners/{companyName}/contactsremovePartnerContact(companyName, username)usingDELETE /partners/{companyName}/contacts/{username}
- Add to
-
Task 2a: User Search API Integration - RED Phase
- Write failing test:
- Write failing test:
-
Task 2b: User Search API Integration - GREEN Phase
- Add to
src/services/api/userApi.tssearchUsers(query)usingGET /users/search?q={query}
- Add to
-
Task 3a: React Query Hooks - RED Phase
- Write failing test:
- Write failing test:
- Write failing test:
- Write failing test:
- Write failing test:
-
Task 3b: React Query Hooks - GREEN Phase
- Create
src/hooks/usePartnerContacts.ts - Create
src/hooks/useAddPartnerContact.ts - Create
src/hooks/useRemovePartnerContact.ts
- Create
-
Task 4a: Zustand Store - RED Phase
- Write failing test:
- Write failing test:
- Write failing test:
-
Task 4b: Zustand Store - GREEN Phase
- Create
-
Task 5a: Contact Card Component - RED Phase
-
Task 5b: Contact Card Component - GREEN Phase
- Create
-
Task 6a: User Autocomplete Component - RED Phase
-
Task 6b: User Autocomplete Component - GREEN Phase
- Create or reuse
-
Task 7a: Contact Role Select Component - RED Phase
-
Task 7b: Contact Role Select Component - GREEN Phase
- Create
-
Task 8a: Add Contact Modal Component - RED Phase
-
Task 8b: Add Contact Modal Component - GREEN Phase
- Create
-
Task 9a: Contact List Component - RED Phase
-
Task 9b: Contact List Component - GREEN Phase
- Create
-
Task 10a: Contacts Tab Component - RED Phase
-
Task 10b: Contacts Tab Component - GREEN Phase
- Update
src/components/organizer/PartnerManagement/PartnerContactsTab.tsx
- Update
-
Task 11a: Remove Contact with Confirmation - RED Phase
-
Task 11b: Remove Contact with Confirmation - GREEN Phase
-
Task 12: Responsive Design
-
Task 13: Accessibility
-
Task 14: Internationalization
-
Task 15: Integration Testing
-
Task 16: E2E Testing
- Create
Dev Notes - Implementation Guide
Service Setup
File Structure:
web-frontend/src/
βββ components/organizer/PartnerManagement/
β βββ PartnerContactsTab.tsx # Tab container (from Story 2.8.2)
β βββ ContactList.tsx # Contact grid
β βββ ContactCard.tsx # Individual contact card
β βββ AddContactModal.tsx # Add contact modal
β βββ EditContactModal.tsx # Edit contact modal (Epic 8?)
β βββ UserAutocomplete.tsx # User search
β βββ ContactRoleSelect.tsx # Role dropdown
β βββ PrimaryContactToggle.tsx # Primary toggle
β βββ index.ts
βββ hooks/
β βββ usePartnerContacts.ts # List contacts
β βββ useAddPartnerContact.ts # Add mutation
β βββ useRemovePartnerContact.ts # Remove mutation
β βββ useSearchUsers.ts # User autocomplete
βββ stores/
β βββ contactModalStore.ts
API Specifications
API Client Extensions (partnerApi.ts):
// Add to src/services/api/partnerApi.ts
export type PartnerContactResponse = components['schemas']['PartnerContactResponse'];
export type AddPartnerContactRequest = components['schemas']['AddPartnerContactRequest'];
export type ContactRole = components['schemas']['ContactRole'];
export const getPartnerContacts = async (companyName: string): Promise<PartnerContactResponse[]> => {
const response = await apiClient.get(`${PARTNER_API_PATH}/${companyName}/contacts`);
// Backend returns contacts enriched with User data via HTTP call
return response.data;
};
export const addPartnerContact = async (
companyName: string,
request: AddPartnerContactRequest
): Promise<PartnerContactResponse> => {
const response = await apiClient.post(`${PARTNER_API_PATH}/${companyName}/contacts`, request);
return response.data;
};
export const removePartnerContact = async (
companyName: string,
username: string
): Promise<void> => {
await apiClient.delete(`${PARTNER_API_PATH}/${companyName}/contacts/${username}`);
};
User API Client (userApi.ts):
// Add to src/services/api/userApi.ts
export const searchUsers = async (query: string): Promise<UserSearchResult[]> => {
const response = await apiClient.get('/users/search', { params: { q: query } });
return response.data;
};
React Query Hooks
usePartnerContacts.ts:
import { useQuery } from '@tanstack/react-query';
import { getPartnerContacts } from '@/services/api/partnerApi';
export const usePartnerContacts = (companyName: string) => {
return useQuery({
queryKey: ['partner', companyName, 'contacts'],
queryFn: () => getPartnerContacts(companyName),
staleTime: 10 * 60 * 1000, // 10 minutes
enabled: !!companyName,
});
};
useAddPartnerContact.ts:
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { addPartnerContact } from '@/services/api/partnerApi';
import { toast } from 'react-toastify';
export const useAddPartnerContact = (companyName: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (request: AddPartnerContactRequest) => addPartnerContact(companyName, request),
onMutate: async (newContact) => {
// Optimistic update
await queryClient.cancelQueries({ queryKey: ['partner', companyName, 'contacts'] });
const previousContacts = queryClient.getQueryData(['partner', companyName, 'contacts']);
queryClient.setQueryData(['partner', companyName, 'contacts'], (old: any) => [
...old,
{ ...newContact, id: 'temp-id' },
]);
return { previousContacts };
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['partner', companyName, 'contacts'] });
toast.success('Contact added successfully');
},
onError: (error: any, newContact, context) => {
// Revert optimistic update
queryClient.setQueryData(['partner', companyName, 'contacts'], context?.previousContacts);
const errorMessage = error.response?.data?.message || 'Failed to add contact';
const correlationId = error.response?.data?.correlationId;
toast.error(`${errorMessage}. Correlation ID: ${correlationId}`);
},
});
};
Contact Role Mapping
const roleConfig: Record<ContactRole, { icon: string; color: string; label: string }> = {
PRIMARY: { icon: 'π€', color: 'primary', label: 'Primary' },
BILLING: { icon: 'π³', color: 'info', label: 'Billing' },
TECHNICAL: { icon: 'π§', color: 'warning', label: 'Technical' },
MARKETING: { icon: 'π§', color: 'success', label: 'Marketing' },
};
Primary Contact Validation
// In AddContactModal.tsx
const hasPrimaryContact = contacts?.some(c => c.isPrimary);
<FormControlLabel
control={
<Switch
checked={isPrimary}
onChange={(e) => setIsPrimary(e.target.checked)}
disabled={hasPrimaryContact}
/>
}
label={
<Tooltip title={hasPrimaryContact ? 'This partner already has a primary contact' : ''}>
<span>Primary Contact</span>
</Tooltip>
}
/>
Confirmation Dialog
const [confirmRemove, setConfirmRemove] = useState<PartnerContactResponse | null>(null);
<ConfirmDialog
open={!!confirmRemove}
title="Remove Contact?"
message={`Are you sure you want to remove ${confirmRemove?.firstName} ${confirmRemove?.lastName} (${confirmRemove?.email}) from this partnership?`}
onConfirm={() => {
removeContactMutation.mutate(confirmRemove.username);
setConfirmRemove(null);
}}
onCancel={() => setConfirmRemove(null)}
/>
Definition of Done Checklist
Development Complete
Frontend Complete
Review Ready
Change Log
| Date | Version | Description | Author |
|---|---|---|---|
| 2025-01-09 | 1.0 | Initial story creation | Bob (Scrum Master) |
Dev Agent Record
Agent Model Used
To be filled by dev agent
Implementation Approach
To be filled by dev agent during implementation
Debug Log References
To be filled by dev agent
Completion Notes
To be filled by dev agent
Files Changed
To be filled by dev agent
Deployment Notes
No special deployment considerations - standard React frontend deployment