Frontend Architecture¶
The Sapari frontend is a React application built with TypeScript and Vite. It uses a feature-based architecture where code is organized by domain feature rather than technical type.
Tech Stack¶
| Technology | Purpose |
|---|---|
| React 19 | UI framework |
| TypeScript | Type safety |
| Vite | Build tool and dev server |
| React Query | Server state management |
| EventSource | SSE for real-time events |
| Tailwind CSS | Styling |
Project Structure¶
frontend/
├── App.tsx # Root component with providers
├── index.html # HTML entry point
├── types.ts # Frontend UI types
├── shell/ # App orchestration layer
│ ├── Dashboard.tsx # Main editor (~1400 lines)
│ ├── DashboardContext.tsx # Dashboard state
│ ├── Sidebar.tsx # Navigation sidebar
│ └── index.ts
├── features/ # Self-contained feature modules
│ ├── analysis/ # Timeline editor, edit navigation
│ ├── assets/ # Asset library, groups, editor
│ ├── auth/ # Login, auth context
│ ├── captions/ # Caption editor, overlay
│ ├── clips/ # Clip CRUD, playback info
│ ├── drafts/ # Draft save/load
│ ├── edits/ # Edit CRUD, undo/redo, cut/mute actions
│ ├── exports/ # Export panel, settings
│ ├── mobile/ # Mobile UI (SwipeReview, FocusMode)
│ ├── projects/ # Project CRUD, SSE events
│ ├── settings/ # Analysis settings, audio censorship, presets
│ ├── upload/ # File upload, YouTube import
│ └── video-player/ # Video preview, timeline hooks, audio muting
├── shared/ # Cross-cutting concerns
│ ├── api/ # API client, React Query setup
│ ├── config/ # Constants, configuration
│ ├── context/ # ThemeContext
│ ├── hooks/ # Shared React hooks
│ ├── lib/ # SSE client, logger, storage
│ ├── types/ # Shared API response types
│ └── ui/ # UI components, icons
└── vite.config.ts
Feature Module Structure¶
Each feature follows a consistent structure:
features/projects/
├── api.ts # API client functions
├── hooks.ts # React Query hooks
├── components/ # Feature-specific components
│ └── ProjectList.tsx
├── context/ # Feature context (if needed)
└── index.ts # Barrel exports
All exports go through index.ts:
// features/projects/index.ts
export { projectApi, projectKeys } from './api';
export { useProjects, useProject, useCreateProject } from './hooks';
export { ProjectList } from './components/ProjectList';
Data Flow¶
The frontend follows a unidirectional data flow pattern:
- Components call hooks (e.g.,
useProjects()) - Hooks use React Query to fetch/mutate data via API services
- React Query manages the cache
- SSE events trigger cache invalidation for real-time updates
API Communication¶
Each feature has its own api.ts with typed functions:
// features/projects/api.ts
export const projectApi = {
list: (page = 1) => api.get<PaginatedResponse<ProjectRead>>(`/projects/?page=${page}`),
get: (uuid: string) => api.get<ProjectRead>(`/projects/${uuid}`),
create: (data: ProjectCreate) => api.post<ProjectRead>('/projects/', data),
analyze: (uuid: string, settings: AnalysisSettings) => api.post(`/projects/${uuid}/analyze`, settings),
};
// Query keys for cache management
export const projectKeys = {
all: ['projects'] as const,
lists: () => [...projectKeys.all, 'list'] as const,
detail: (uuid: string) => [...projectKeys.all, 'detail', uuid] as const,
};
The Vite dev server proxies /api requests to the backend, so we use relative URLs.
Hooks Pattern¶
Each feature has a hooks.ts file with React Query hooks:
// features/projects/hooks.ts
export function useProjects() {
return useQuery({
queryKey: projectKeys.lists(),
queryFn: async () => {
const response = await projectApi.list();
return response.data.map(toFrontendProject);
},
});
}
export function useCreateProject() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (name: string) => projectApi.create({ name }),
onSuccess: (newProject) => {
queryClient.setQueryData(projectKeys.lists(), (old) => [newProject, ...old]);
},
});
}
Path Aliases¶
TypeScript path aliases simplify imports:
// tsconfig.json paths
{
"@/*": ["./*"],
"@/features/*": ["./features/*"],
"@/shared/*": ["./shared/*"],
"@/shell/*": ["./shell/*"],
"@/types": ["./types.ts"]
}
// Usage
import { useProjects } from '@/features/projects';
import { Button } from '@/shared/ui';
import { Dashboard } from '@/shell';
import type { Project } from '@/types';
Development¶
Run the frontend dev server:
This starts Vite on port 3000 with hot reload. API requests are proxied to http://localhost:8000.
Key Files¶
| Component | Location |
|---|---|
| Entry point | frontend/index.html |
| App component | frontend/App.tsx |
| Main dashboard | frontend/shell/Dashboard.tsx |
| Type definitions | frontend/types.ts |
| Vite config | frontend/vite.config.ts |
| API client | frontend/shared/api/client.ts |
| SSE client | frontend/shared/lib/events.ts |
See also: Mobile Interface for SwipeReview and FocusMode documentation.