diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..1080187
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import { QueryClientProvider } from '@tanstack/react-query'
+import { queryClient } from './lib/queries'
+import { Film } from 'lucide-react'
+
+const VideoPlaceholder: React.FC = () => {
+ return (
+
+
+
+
Video upload coming in Phase 2
+
+
+ )
+}
+
+const RightTop: React.FC = () => {
+ return
+}
+
+const BottomArea: React.FC = () => {
+ return
+}
+
+const AppLayout: React.FC = () => {
+ return (
+
+ )
+}
+
+export default function App(): JSX.Element {
+ return (
+
+
+
+ )
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..b83f6d0
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { createRoot } from 'react-dom/client'
+import App from './App'
+import './styles.css'
+
+const root = createRoot(document.getElementById('root')!)
+root.render(
+
+
+
+)
diff --git a/frontend/src/test/components/Layout.test.tsx b/frontend/src/test/components/Layout.test.tsx
new file mode 100644
index 0000000..ac9e532
--- /dev/null
+++ b/frontend/src/test/components/Layout.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import App from '../../App'
+
+describe('Layout', () => {
+ test('renders VideoPlaceholder with Phase 2 text', () => {
+ render()
+ const text = screen.getByText(/Video upload coming in Phase 2/i)
+ expect(text).toBeInTheDocument()
+ })
+})
diff --git a/frontend/src/test/lib/api.test.ts b/frontend/src/test/lib/api.test.ts
new file mode 100644
index 0000000..a64ee64
--- /dev/null
+++ b/frontend/src/test/lib/api.test.ts
@@ -0,0 +1,23 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { apiClient, queryDocument, ingestDocument } from '../../lib/api'
+
+describe('API client basics', () => {
+ it('has a baseURL containing /api/v1', () => {
+ expect(apiClient.defaults.baseURL).toContain('/api/v1')
+ })
+
+ it('queryDocument posts and returns data', async () => {
+ const mockPost = vi.spyOn(apiClient, 'post')
+ mockPost.mockResolvedValueOnce({ data: { keywords: [], answer: '', sources: [] } })
+ const res = await queryDocument({ question: 'test' })
+ expect(res).toHaveProperty('answer')
+ })
+
+ it('ingestDocument posts and returns data', async () => {
+ const mockPost = vi.spyOn(apiClient, 'post')
+ mockPost.mockResolvedValueOnce({ data: { document_id: 'doc1', chunk_count: 1, filename: 'a.txt' } })
+ const file = new File(['data'], 'a.txt', { type: 'text/plain' })
+ const res = await ingestDocument(file)
+ expect(res).toHaveProperty('document_id')
+ })
+})
diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts
new file mode 100644
index 0000000..c44951a
--- /dev/null
+++ b/frontend/src/test/setup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom'