MAESTRO: Initialize frontend routing with 8 placeholder page components and vitest test suite
Add SetupPage, LoginPage, DashboardPage, ProjectsPage, ExperimentPage, LivePage, ComparePage, and AdminPage as placeholder components. Wire up react-router-dom routing in App.tsx with BrowserRouter in main.tsx. Unknown routes redirect to dashboard. Install vitest + @testing-library/react and add 9 routing tests. Build passes cleanly.
This commit is contained in:
parent
267091bbce
commit
4cd0b8a1c8
17 changed files with 4120 additions and 9 deletions
|
|
@ -35,9 +35,11 @@ Set up the PromptLooper repository, Docker infrastructure, and basic project ske
|
|||
- [x] Create backend/auth.py implementing JWT token generation/verification, API key validation, and the first-boot setup flow. The setup endpoint should check if any users exist — if not, accept username + password to create the admin account. Include a dependency function for route-level auth that supports both JWT and API key.
|
||||
> Created backend/auth.py with: bcrypt password hashing via passlib, JWT token creation/verification (HS256, 24h expiry) using python-jose, first-boot `needs_setup()` + `create_admin()` flow (409 if admin exists), `authenticate_user()` for login, and `get_current_user` FastAPI dependency supporting both JWT Bearer tokens and X-Api-Key header (API key grants first admin user). UUID string-to-UUID conversion for SQLite compatibility. 21 tests in tests/test_auth.py covering hashing, JWT lifecycle, setup flow, login, and all auth dependency paths. All 95 backend tests pass.
|
||||
|
||||
- [ ] Scaffold all router files in backend/routers/ as stubs: auth.py, projects.py, experiments.py, runs.py, endpoints.py, export.py, webhooks.py, admin.py. Each should have the correct APIRouter prefix and tags, with placeholder endpoints that return 501 Not Implemented.
|
||||
- [x] Scaffold all router files in backend/routers/ as stubs: auth.py, projects.py, experiments.py, runs.py, endpoints.py, export.py, webhooks.py, admin.py. Each should have the correct APIRouter prefix and tags, with placeholder endpoints that return 501 Not Implemented.
|
||||
> Created all 8 router stubs with APIRouter instances, mounted via main.py's _mount_routers(). Endpoints match the spec: auth (3 endpoints), projects (5), experiments (9 incl. sweep/pause/resume/stop), runs (5 incl. leaderboard), endpoints (5 incl. test), export (4 formats), webhooks (3), admin (3). All return 501 Not Implemented. 37 tests in tests/test_routers.py verify every route is mounted and returns 501. All 132 backend tests pass.
|
||||
|
||||
- [ ] Initialize the frontend: run npm create vite@latest with React + TypeScript template. Install Tailwind CSS and configure it. Install react-router-dom for routing. Create the basic App.tsx with routes for Setup, Login, Dashboard, Projects, Experiment, Live, Compare, and Admin pages (all as placeholder components). Verify it builds cleanly.
|
||||
- [x] Initialize the frontend: run npm create vite@latest with React + TypeScript template. Install Tailwind CSS and configure it. Install react-router-dom for routing. Create the basic App.tsx with routes for Setup, Login, Dashboard, Projects, Experiment, Live, Compare, and Admin pages (all as placeholder components). Verify it builds cleanly.
|
||||
> Frontend was already scaffolded with Vite + React + TypeScript + Tailwind + react-router-dom from the Dockerfile task. Added 8 placeholder page components (SetupPage, LoginPage, DashboardPage, ProjectsPage, ExperimentPage, LivePage, ComparePage, AdminPage) in frontend/src/pages/. Updated App.tsx with react-router-dom Routes and main.tsx with BrowserRouter. Unknown routes redirect to dashboard. Installed vitest + @testing-library/react for testing. 9 routing tests in App.test.tsx all passing. Build completes cleanly. All 132 backend tests still pass.
|
||||
|
||||
- [ ] Create frontend/src/api/client.ts with a typed API client using fetch. Include JWT token management (stored in memory, not localStorage), request/response interceptors for auth headers, and typed wrapper functions for each API endpoint group. Include WebSocket connection helper.
|
||||
|
||||
|
|
|
|||
3948
frontend/package-lock.json
generated
Normal file
3948
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,8 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
|
|
@ -14,13 +15,17 @@
|
|||
"react-router-dom": "^6.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"jsdom": "^29.0.2",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^6.0.0"
|
||||
"vite": "^6.0.0",
|
||||
"vitest": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
frontend/src/App.test.tsx
Normal file
59
frontend/src/App.test.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import App from "./App";
|
||||
|
||||
function renderWithRouter(route: string) {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={[route]}>
|
||||
<App />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("App routing", () => {
|
||||
it("renders SetupPage at /setup", () => {
|
||||
renderWithRouter("/setup");
|
||||
expect(screen.getByText("PromptLooper Setup")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders LoginPage at /login", () => {
|
||||
renderWithRouter("/login");
|
||||
expect(screen.getByText("Sign In")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders DashboardPage at /", () => {
|
||||
renderWithRouter("/");
|
||||
expect(screen.getByText("Dashboard")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders ProjectsPage at /projects", () => {
|
||||
renderWithRouter("/projects");
|
||||
expect(screen.getByText("Projects")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders ExperimentPage at /experiments/:id", () => {
|
||||
renderWithRouter("/experiments/abc-123");
|
||||
expect(screen.getByText("Experiment")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders LivePage at /live/:id", () => {
|
||||
renderWithRouter("/live/abc-123");
|
||||
expect(screen.getByText("Live")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders ComparePage at /compare", () => {
|
||||
renderWithRouter("/compare");
|
||||
expect(screen.getByText("Compare")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders AdminPage at /admin", () => {
|
||||
renderWithRouter("/admin");
|
||||
expect(screen.getByText("Admin")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("redirects unknown routes to dashboard", () => {
|
||||
renderWithRouter("/nonexistent");
|
||||
expect(screen.getByText("Dashboard")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,25 @@
|
|||
function App() {
|
||||
return <div>PromptLooper</div>;
|
||||
}
|
||||
import { Routes, Route, Navigate } from "react-router-dom";
|
||||
import SetupPage from "./pages/SetupPage";
|
||||
import LoginPage from "./pages/LoginPage";
|
||||
import DashboardPage from "./pages/DashboardPage";
|
||||
import ProjectsPage from "./pages/ProjectsPage";
|
||||
import ExperimentPage from "./pages/ExperimentPage";
|
||||
import LivePage from "./pages/LivePage";
|
||||
import ComparePage from "./pages/ComparePage";
|
||||
import AdminPage from "./pages/AdminPage";
|
||||
|
||||
export default App;
|
||||
export default function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/setup" element={<SetupPage />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/" element={<DashboardPage />} />
|
||||
<Route path="/projects" element={<ProjectsPage />} />
|
||||
<Route path="/experiments/:id" element={<ExperimentPage />} />
|
||||
<Route path="/live/:id" element={<LivePage />} />
|
||||
<Route path="/compare" element={<ComparePage />} />
|
||||
<Route path="/admin" element={<AdminPage />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
|
|
|||
8
frontend/src/pages/AdminPage.tsx
Normal file
8
frontend/src/pages/AdminPage.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default function AdminPage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Admin</h1>
|
||||
<p className="text-gray-600">System administration and user management.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
frontend/src/pages/ComparePage.tsx
Normal file
8
frontend/src/pages/ComparePage.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default function ComparePage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Compare</h1>
|
||||
<p className="text-gray-600">Compare results across runs and experiments.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
frontend/src/pages/DashboardPage.tsx
Normal file
8
frontend/src/pages/DashboardPage.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default function DashboardPage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Dashboard</h1>
|
||||
<p className="text-gray-600">Overview of recent experiments and runs.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
frontend/src/pages/ExperimentPage.tsx
Normal file
8
frontend/src/pages/ExperimentPage.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default function ExperimentPage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Experiment</h1>
|
||||
<p className="text-gray-600">Configure and run prompt experiments.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
frontend/src/pages/LivePage.tsx
Normal file
8
frontend/src/pages/LivePage.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default function LivePage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Live</h1>
|
||||
<p className="text-gray-600">Real-time experiment progress and results.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
frontend/src/pages/LoginPage.tsx
Normal file
10
frontend/src/pages/LoginPage.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export default function LoginPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-50">
|
||||
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow">
|
||||
<h1 className="mb-4 text-2xl font-bold">Sign In</h1>
|
||||
<p className="text-gray-600">Log in to PromptLooper.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
frontend/src/pages/ProjectsPage.tsx
Normal file
8
frontend/src/pages/ProjectsPage.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default function ProjectsPage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="mb-4 text-2xl font-bold">Projects</h1>
|
||||
<p className="text-gray-600">Manage your prompt tuning projects.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
10
frontend/src/pages/SetupPage.tsx
Normal file
10
frontend/src/pages/SetupPage.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export default function SetupPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-50">
|
||||
<div className="w-full max-w-md rounded-lg bg-white p-8 shadow">
|
||||
<h1 className="mb-4 text-2xl font-bold">PromptLooper Setup</h1>
|
||||
<p className="text-gray-600">Create your admin account to get started.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
frontend/src/test-setup.ts
Normal file
1
frontend/src/test-setup.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
import "@testing-library/jest-dom/vitest";
|
||||
|
|
@ -17,4 +17,9 @@ export default defineConfig({
|
|||
"/health": "http://localhost:8000",
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: ["./src/test-setup.ts"],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue