diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index a5f992d..cf7cd3d 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -19,7 +19,7 @@ Focused tests:
```bash
cd backend && go test ./internal/service -run '^TestCandidatesRequireSixtyPercent$'
cd backend && go test -tags=integration ./internal/integration -run '^TestCompleteInterviewWorkflow$'
-cd frontend && npm test -- src/App.test.tsx -t "filters jobs by title"
+cd frontend && npm test -- src/app/App.test.tsx -t "filters jobs by title"
```
The required toolchain is Go 1.27.1 and React 19.3 with TypeScript/Vite. Use
@@ -37,9 +37,12 @@ The required toolchain is Go 1.27.1 and React 19.3 with TypeScript/Vite. Use
`go-guess.interview-notes` RabbitMQ fanout exchange. Each API instance consumes
through its own exclusive queue and distributes events to authenticated SSE
subscribers.
-- `frontend/src/App.tsx` owns routing/layout/screens, `src/api.ts` is the typed HTTP
- boundary, and `src/types.ts` contains client domain types. Vite proxies `/api` to
- the Go service in development.
+- `frontend/src/app` owns application routing and providers, `src/features` groups
+ feature pages and components, `src/components` contains reusable UI,
+ `src/api/client.ts` is the typed HTTP boundary, `src/types` contains client
+ domain types, and `src/styles/global.css` contains application-wide styles.
+ Keep feature-specific components inside their feature directory. Vite proxies
+ `/api` to the Go service in development.
Goose files in `backend/migrations` are schema-only and run in every environment.
`internal/database/seed/common.sql` contains shared reference data;
diff --git a/README.md b/README.md
index 7d01334..9097176 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ cd backend && go test ./internal/service -run '^TestCandidatesRequireSixtyPercen
Run one frontend test:
```bash
-cd frontend && npm test -- src/App.test.tsx -t "filters jobs by title"
+cd frontend && npm test -- src/app/App.test.tsx -t "filters jobs by title"
```
Docker must be available for `make test`. The integration suite starts PostgreSQL
diff --git a/frontend/README.md b/frontend/README.md
index b5aff32..a259c1c 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -18,9 +18,21 @@ npm run build
Run one test:
```bash
-npm test -- src/App.test.tsx -t "shows answer inputs"
+npm test -- src/app/App.test.tsx -t "filters jobs by title"
```
+## Source structure
+
+- `src/app` contains the application shell, providers, routes, and route-level tests.
+- `src/features` groups pages and components by business capability.
+- `src/components` contains UI shared by multiple features.
+- `src/api/client.ts` is the typed HTTP boundary.
+- `src/types` contains API and domain types.
+- `src/styles/global.css` contains shared tokens and layout styles.
+
+Keep feature-only UI close to its feature instead of adding more screens to the
+application shell.
+
## API integration
Vite proxies `/api` to `http://localhost:8080`. Login stores the returned JWT in
diff --git a/frontend/src/api.test.ts b/frontend/src/api/client.test.ts
similarity index 97%
rename from frontend/src/api.test.ts
rename to frontend/src/api/client.test.ts
index a74dbf5..03f4780 100644
--- a/frontend/src/api.test.ts
+++ b/frontend/src/api/client.test.ts
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { api, authStorage } from './api'
+import { api, authStorage } from './client'
describe('API client', () => {
beforeEach(() => {
diff --git a/frontend/src/api.ts b/frontend/src/api/client.ts
similarity index 99%
rename from frontend/src/api.ts
rename to frontend/src/api/client.ts
index d05c221..75ea02c 100644
--- a/frontend/src/api.ts
+++ b/frontend/src/api/client.ts
@@ -15,7 +15,7 @@ import type {
ScheduledInterview,
User,
InterviewNote,
-} from './types'
+} from '../types'
const TOKEN_KEY = 'go-guess-token'
const USER_KEY = 'go-guess-user'
diff --git a/frontend/src/App.test.tsx b/frontend/src/app/App.test.tsx
similarity index 99%
rename from frontend/src/App.test.tsx
rename to frontend/src/app/App.test.tsx
index cd10d08..35643f1 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/app/App.test.tsx
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'
import { BrowserRouter, MemoryRouter } from 'react-router-dom'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import App from './App'
-import type { Job, Participant } from './types'
+import type { Job, Participant } from '../types'
const question = {
id: 11,
@@ -680,6 +680,8 @@ describe('Go Guess frontend', () => {
renderApp('/jobs/7')
await user.click(await screen.findByRole('tab', { name: /invitations/i }))
await user.click(screen.getByRole('button', { name: 'Schedule Amélie Dubois' }))
+ expect(screen.getByRole('heading', { name: 'Interview details' })).toBeInTheDocument()
+ expect(screen.getByRole('heading', { name: 'Interview team' })).toBeInTheDocument()
await user.type(screen.getByLabelText('Date and time'), '2026-09-22T10:30')
await user.type(screen.getByLabelText('Location or meeting link'), 'Brussels')
await user.click(await screen.findByLabelText(/Alex Morgan/))
diff --git a/frontend/src/App.tsx b/frontend/src/app/App.tsx
similarity index 99%
rename from frontend/src/App.tsx
rename to frontend/src/app/App.tsx
index d263176..4b5cd4e 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/app/App.tsx
@@ -43,15 +43,15 @@ import {
useNavigate,
useParams,
} from 'react-router-dom'
-import { api, ApiError, authStorage } from './api'
+import { api, ApiError, authStorage } from '../api/client'
import {
CalendarPage,
InboxPage,
ParticipantMeetingPage,
ScheduleInterviewForm,
ScheduledInterviewPage,
- UsersPage,
-} from './interview-workflow'
+} from '../features/interviews'
+import { UsersPage } from '../features/users'
import type {
CandidateMatch,
CreateJobInput,
@@ -65,7 +65,7 @@ import type {
Question,
QuestionType,
ScheduledInterview,
-} from './types'
+} from '../types'
interface AppData {
jobs: Job[]
diff --git a/frontend/src/components/ui/AsyncState.tsx b/frontend/src/components/ui/AsyncState.tsx
new file mode 100644
index 0000000..5a928ee
--- /dev/null
+++ b/frontend/src/components/ui/AsyncState.tsx
@@ -0,0 +1,17 @@
+export function ErrorAlert({ error }: { error: string }) {
+ return (
+