Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ Do not expose the development configuration directly to the public internet. Rev
## Documentation and contributing

- [Documentation index](docs/README.md) — current public documentation and reading paths.
- [Iteration 1 engineer feedback](docs/operations/ITERATION_1_ENGINEER_FEEDBACK.md) — test the current end-to-end workflow and report evidence safely.
- [System Overview](docs/architecture/SYSTEM_OVERVIEW.md) — components, runtime flow, persistence, and trust boundaries.
- [Repository Intelligence](docs/architecture/REPOSITORY_INTELLIGENCE.md) — extraction, snapshot, consumer, and evidence rules.
- [Accepted `ri.v1` RFC](docs/architecture/REPOSITORY_INTELLIGENCE_V1_RFC.md) — the versioned snapshot contract.
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/e2e/accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ test.describe('WCAG 2.2 AA automated baseline (#118)', () => {
await openSurface(page, 'Repositories');
await page.getByRole('button', { name: 'Upload New' }).click();
await expect(page.getByRole('heading', { name: 'Upload Repository' })).toBeVisible();
await page.getByRole('button', { name: 'GitHub URL', exact: true }).click();
await page.getByRole('tab', { name: 'GitHub URL', exact: true }).click();
await expect(page.getByText('Import from GitHub')).toBeVisible();
await expectWcagBaseline(page, 'repository import: empty GitHub URL form', [
...SHELL_FINDINGS,
Expand Down
7 changes: 3 additions & 4 deletions apps/frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<title>PARTHA - Understand Any Codebase in Minutes</title>
<title>PARTHA — Repository Intelligence</title>
</head>
<body class="dark">
<body>
<div id="root"></div>
<script type="module" src="/src/app/main.tsx"></script>
</body>
Expand Down
24 changes: 24 additions & 0 deletions apps/frontend/src/app/pages/AIWorkspacePage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { describe, expect, it, vi } from 'vitest';
import { AIWorkspacePage } from './AIWorkspacePage';

vi.mock('@/features/ai/hooks/useAIWorkspace', () => ({
useAIWorkspace: () => ({
activeRepository: { id: 'repo-1', name: 'partha', source: 'upload' },
source: 'upload', emptyReason: null, query: '', setQuery: vi.fn(),
messages: [], suggestions: [], loading: false, error: null,
historyError: false, providerConfigured: false, ask: vi.fn(), retryLoad: vi.fn(),
}),
}));

describe('AIWorkspacePage composer', () => {
it('keeps the real provider setup state and question composer available', () => {
render(<MemoryRouter><AIWorkspacePage /></MemoryRouter>);

expect(screen.getByRole('heading', { name: 'AI Workspace' })).toBeVisible();
expect(screen.getByText(/No AI provider is configured/)).toBeVisible();
expect(screen.getByLabelText('Ask about the codebase')).toBeVisible();
expect(screen.getByRole('button', { name: /Settings/i })).toBeVisible();
});
});
14 changes: 8 additions & 6 deletions apps/frontend/src/app/pages/AIWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ export function AIWorkspacePage() {
}

return (
<div className="flex flex-col h-[calc(100vh-8rem)]">
<div className="flex h-[calc(100dvh-7rem)] min-h-[460px] min-w-0 flex-col">
<PageHeader title="AI Workspace" description={`AI-powered exploration of ${activeRepository.name}`}>
<DataSourceBadge source={aiWorkspace.source} />
</PageHeader>
<PreviewBanner limitation={AI_WORKSPACE_LIMITATION} />

<div className="flex-1 flex flex-col rounded-xl border border-border bg-card overflow-hidden">
<div className="flex-1 overflow-y-auto p-5 space-y-4 scrollbar-thin">
<div className="partha-surface flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto p-4 scrollbar-thin sm:p-5">
{aiWorkspace.messages.length === 0 ? (
<div className="flex h-full items-center justify-center text-center">
<div className="max-w-sm">
Expand All @@ -65,7 +65,7 @@ export function AIWorkspacePage() {
</div>
<p className="text-sm font-medium text-foreground mb-1">Ask about {activeRepository.name}</p>
<p className="text-xs text-muted-foreground">
Questions are sent to your configured AI provider with repository context and file citations when available.
Questions are sent to your configured provider with structural facts from the sealed snapshot. Source-file contents are not sent.
</p>
</div>
</div>
Expand Down Expand Up @@ -123,7 +123,7 @@ export function AIWorkspacePage() {
</div>
)}
</div>
<div className="border-t border-border p-4">
<div className="shrink-0 border-t border-border bg-card p-3 sm:p-4">
{aiWorkspace.providerConfigured === false && (
<div className="mb-3 flex items-center gap-2 rounded-lg border border-amber-500/40 bg-amber-500/5 px-4 py-2.5 text-sm text-amber-200">
<Settings className="h-4 w-4 shrink-0" />
Expand All @@ -147,12 +147,14 @@ export function AIWorkspacePage() {
void aiWorkspace.ask();
}}
>
<label htmlFor="ai-workspace-query" className="sr-only">Ask about the codebase</label>
<input
id="ai-workspace-query"
type="text"
placeholder="Ask about the codebase..."
value={aiWorkspace.query}
onChange={(e) => aiWorkspace.setQuery(e.target.value)}
className="flex-1 rounded-md border border-border bg-background px-4 py-2.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
className="partha-input min-w-0 flex-1 px-3 py-2.5 text-sm sm:px-4"
/>
<button
disabled={!canSend}
Expand Down
8 changes: 4 additions & 4 deletions apps/frontend/src/app/pages/ArchitecturePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ export function ArchitecturePage() {
}

return (
<div className="h-[calc(100vh-8rem)] -m-6 flex flex-col">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border px-4 py-3 sm:px-6">
<div className="-m-3 flex h-[calc(100dvh-4rem)] min-h-[560px] min-w-0 flex-col sm:-m-5 lg:-m-6">
<div className="flex min-w-0 flex-col items-start justify-between gap-2 border-b border-border px-3 py-3 sm:flex-row sm:flex-wrap sm:items-center sm:px-5 lg:px-6">
<h1 className="min-w-0 truncate text-sm font-medium text-foreground">Architecture - {architecture.model.repositoryName}</h1>
<div className="flex flex-wrap items-center gap-2">
<div className="flex max-w-full flex-wrap items-center gap-2">
<Link
to="/review"
className="inline-flex items-center gap-1.5 rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-accent/40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary"
Expand All @@ -114,7 +114,7 @@ export function ArchitecturePage() {
</div>
{/* The manifest names the exact revision every citation below belongs
to, so it sits with the evidence rather than in a settings page. */}
<div className="mb-4">
<div className="mb-3 min-w-0">
<RevisionManifestPanel repositoryId={architecture.model.repositoryId} />
</div>
<div className="flex-1 min-h-0">
Expand Down
12 changes: 6 additions & 6 deletions apps/frontend/src/app/pages/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ export function DashboardPage() {
if (repo.status === 'analysing' || repo.status === 'cancelled') navigate(`/analysis/${repo.id}`);
else navigate(`/repositories/${repo.id}`);
}}
className="flex items-center justify-between px-5 py-3.5 hover:bg-accent/30 cursor-pointer transition-colors"
className="flex min-w-0 flex-col items-start justify-between gap-3 px-4 py-3.5 hover:bg-accent/30 cursor-pointer transition-colors sm:flex-row sm:items-center sm:px-5"
>
<div className="flex items-center gap-3">
<div className="flex min-w-0 items-center gap-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-muted">
{repo.source === 'github' ? (
<Github className="h-4 w-4 text-muted-foreground" />
) : (
<FolderGit2 className="h-4 w-4 text-muted-foreground" />
)}
</div>
<div>
<p className="text-sm font-medium text-foreground">{repo.name}</p>
<div className="flex items-center gap-2 mt-0.5">
<div className="min-w-0">
<p className="truncate text-sm font-medium text-foreground">{repo.name}</p>
<div className="mt-0.5 flex min-w-0 flex-wrap items-center gap-2">
{repo.meta?.language && (
<span className="text-xs text-muted-foreground">{repo.meta.language}</span>
)}
Expand All @@ -91,7 +91,7 @@ export function DashboardPage() {
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex max-w-full flex-wrap items-center gap-3 sm:justify-end">
<DataSourceBadge source={repo.source} />
{repo.meta && (
<span className="text-xs text-muted-foreground hidden sm:inline">
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/app/pages/EngineeringReviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function EngineeringReviewPage() {
No overall score, grade, health percentage, or category score is produced.
</p>
</div>
<div className="grid grid-cols-5 gap-2">
<div className="grid w-full grid-cols-2 gap-2 sm:grid-cols-5 lg:w-auto">
{SEVERITY_ORDER.map((severity) => (
<div key={severity} className="rounded-md bg-muted/50 px-2 py-2 text-center">
<p className="text-base font-semibold text-foreground">
Expand Down
40 changes: 13 additions & 27 deletions apps/frontend/src/app/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { Link } from 'react-router-dom';
import { Hexagon, Loader2 } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { useLoginForm } from '@/features/auth/hooks/useLoginForm';
import { AuthShell } from '@/shared/components/layout/AuthShell';

export function LoginPage() {
const { email, setEmail, password, setPassword, submitting, error, submit, redirectState } = useLoginForm();

return (
<div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-sm">
<div className="mb-8 flex flex-col items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground">
<Hexagon className="h-5 w-5" />
</div>
<h1 className="text-lg font-semibold text-foreground">Sign in to PARTHA</h1>
</div>

<form onSubmit={submit} className="space-y-4 rounded-xl border border-border bg-card p-6">
<AuthShell
eyebrow="Welcome back"
title="Sign in to PARTHA"
description="Continue to your repository intelligence workspace."
footer={<>Don&apos;t have an account?{' '}<Link to="/register" state={redirectState} data-testid="login-register-link" className="font-medium text-primary hover:underline">Create one</Link></>}
>
<form onSubmit={submit} className="space-y-4">
<div>
<label htmlFor="login-email" className="block text-xs font-medium text-muted-foreground mb-1.5">
Email
Expand All @@ -27,7 +25,7 @@ export function LoginPage() {
required
value={email}
onChange={(event) => setEmail(event.target.value)}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
className="partha-input w-full px-3 py-2.5 text-sm"
/>
</div>
<div>
Expand All @@ -41,7 +39,7 @@ export function LoginPage() {
required
value={password}
onChange={(event) => setPassword(event.target.value)}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
className="partha-input w-full px-3 py-2.5 text-sm"
/>
</div>

Expand All @@ -54,25 +52,13 @@ export function LoginPage() {
<button
type="submit"
disabled={submitting}
className="flex w-full items-center justify-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
className="flex w-full items-center justify-center gap-2 rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
>
{submitting && <Loader2 className="h-4 w-4 animate-spin" />}
{submitting ? 'Signing in...' : 'Sign in'}
</button>
</form>

<p className="mt-4 text-center text-sm text-muted-foreground">
Don&apos;t have an account?{' '}
<Link
to="/register"
state={redirectState}
data-testid="login-register-link"
className="text-primary hover:underline"
>
Create one
</Link>
</p>
</div>
</div>
</AuthShell>
);
}
35 changes: 13 additions & 22 deletions apps/frontend/src/app/pages/RegisterPage.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { Link } from 'react-router-dom';
import { Hexagon, Loader2 } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { PASSWORD_MIN_LENGTH, useRegisterForm } from '@/features/auth/hooks/useRegisterForm';
import { AuthShell } from '@/shared/components/layout/AuthShell';

export function RegisterPage() {
const { email, setEmail, password, setPassword, submitting, error, submit, redirectState } = useRegisterForm();

return (
<div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-sm">
<div className="mb-8 flex flex-col items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-primary-foreground">
<Hexagon className="h-5 w-5" />
</div>
<h1 className="text-lg font-semibold text-foreground">Create your PARTHA account</h1>
</div>

<form onSubmit={submit} className="space-y-4 rounded-xl border border-border bg-card p-6">
<AuthShell
eyebrow="Start exploring"
title="Create your PARTHA account"
description="Build a private, evidence-backed view of your codebase."
footer={<>Already have an account?{' '}<Link to="/login" state={redirectState} className="font-medium text-primary hover:underline">Sign in</Link></>}
>
<form onSubmit={submit} className="space-y-4">
<div>
<label htmlFor="register-email" className="block text-xs font-medium text-muted-foreground mb-1.5">
Email
Expand All @@ -27,7 +25,7 @@ export function RegisterPage() {
required
value={email}
onChange={(event) => setEmail(event.target.value)}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
className="partha-input w-full px-3 py-2.5 text-sm"
/>
</div>
<div>
Expand All @@ -42,7 +40,7 @@ export function RegisterPage() {
minLength={PASSWORD_MIN_LENGTH}
value={password}
onChange={(event) => setPassword(event.target.value)}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
className="partha-input w-full px-3 py-2.5 text-sm"
/>
<p className="mt-1.5 text-2xs text-muted-foreground">At least {PASSWORD_MIN_LENGTH} characters.</p>
</div>
Expand All @@ -56,20 +54,13 @@ export function RegisterPage() {
<button
type="submit"
disabled={submitting}
className="flex w-full items-center justify-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
className="flex w-full items-center justify-center gap-2 rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
>
{submitting && <Loader2 className="h-4 w-4 animate-spin" />}
{submitting ? 'Creating account...' : 'Create account'}
</button>
</form>

<p className="mt-4 text-center text-sm text-muted-foreground">
Already have an account?{' '}
<Link to="/login" state={redirectState} className="text-primary hover:underline">
Sign in
</Link>
</p>
</div>
</div>
</AuthShell>
);
}
4 changes: 2 additions & 2 deletions apps/frontend/src/app/pages/RepositoriesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export function RepositoriesPage() {
</div>
)}

<div className="rounded-xl border border-border bg-card overflow-hidden">
<table className="w-full">
<div className="rounded-xl border border-border bg-card overflow-x-auto scrollbar-thin">
<table className="w-full min-w-[560px]">
<thead>
<tr className="border-b border-border">
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Name</th>
Expand Down
Loading
Loading