diff --git a/README.md b/README.md index 8c2133b..3aa99dd 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/apps/frontend/e2e/accessibility.spec.ts b/apps/frontend/e2e/accessibility.spec.ts index 1621742..c367ab9 100644 --- a/apps/frontend/e2e/accessibility.spec.ts +++ b/apps/frontend/e2e/accessibility.spec.ts @@ -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, diff --git a/apps/frontend/index.html b/apps/frontend/index.html index 16b2fd4..daca798 100644 --- a/apps/frontend/index.html +++ b/apps/frontend/index.html @@ -1,15 +1,14 @@ - + - - PARTHA - Understand Any Codebase in Minutes + PARTHA — Repository Intelligence - +
diff --git a/apps/frontend/src/app/pages/AIWorkspacePage.test.tsx b/apps/frontend/src/app/pages/AIWorkspacePage.test.tsx new file mode 100644 index 0000000..efe26b8 --- /dev/null +++ b/apps/frontend/src/app/pages/AIWorkspacePage.test.tsx @@ -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(); + + 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(); + }); +}); diff --git a/apps/frontend/src/app/pages/AIWorkspacePage.tsx b/apps/frontend/src/app/pages/AIWorkspacePage.tsx index 2485612..e6c76e6 100644 --- a/apps/frontend/src/app/pages/AIWorkspacePage.tsx +++ b/apps/frontend/src/app/pages/AIWorkspacePage.tsx @@ -49,14 +49,14 @@ export function AIWorkspacePage() { } return ( -
+
-
-
+
+
{aiWorkspace.messages.length === 0 ? (
@@ -65,7 +65,7 @@ export function AIWorkspacePage() {

Ask about {activeRepository.name}

- 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.

@@ -123,7 +123,7 @@ export function AIWorkspacePage() {
)}
-
+
{aiWorkspace.providerConfigured === false && (
@@ -147,12 +147,14 @@ export function AIWorkspacePage() { void aiWorkspace.ask(); }} > + 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" /> -

- Don't have an account?{' '} - - Create one - -

-
-
+ ); } diff --git a/apps/frontend/src/app/pages/RegisterPage.tsx b/apps/frontend/src/app/pages/RegisterPage.tsx index 747c1f6..1adf163 100644 --- a/apps/frontend/src/app/pages/RegisterPage.tsx +++ b/apps/frontend/src/app/pages/RegisterPage.tsx @@ -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 ( -
-
-
-
- -
-

Create your PARTHA account

-
- -
+ Already have an account?{' '}Sign in} + > +
@@ -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" />

At least {PASSWORD_MIN_LENGTH} characters.

@@ -56,20 +54,13 @@ export function RegisterPage() { -

- Already have an account?{' '} - - Sign in - -

-
-
+ ); } diff --git a/apps/frontend/src/app/pages/RepositoriesPage.tsx b/apps/frontend/src/app/pages/RepositoriesPage.tsx index 50abfd8..67c04f4 100644 --- a/apps/frontend/src/app/pages/RepositoriesPage.tsx +++ b/apps/frontend/src/app/pages/RepositoriesPage.tsx @@ -74,8 +74,8 @@ export function RepositoriesPage() {
)} -
- +
+
diff --git a/apps/frontend/src/app/pages/SettingsPage.tsx b/apps/frontend/src/app/pages/SettingsPage.tsx index e368938..ae8f94d 100644 --- a/apps/frontend/src/app/pages/SettingsPage.tsx +++ b/apps/frontend/src/app/pages/SettingsPage.tsx @@ -16,13 +16,16 @@ export function SettingsPage() { ] as const; return ( -
+
-
+
{tabs.map((tab) => (
)} {activeTab === 'AI Providers' && ( -
-

AI Provider

+
+
+

AI Provider

+ + {settings.aiConfig?.provider ? `Saved: ${settings.aiConfig.provider}` : 'Not configured'} + +

Keys are stored by the local backend and are never shown again after saving.

-
+
{providers.map(([id, label]) => (
- + {settings.error &&

{settings.error}

} {settings.statusMessage &&

{settings.statusMessage}

} -
- -
@@ -159,13 +174,13 @@ export function SettingsPage() {

Theme

-
diff --git a/apps/frontend/src/app/pages/UploadPage.test.tsx b/apps/frontend/src/app/pages/UploadPage.test.tsx new file mode 100644 index 0000000..05df23b --- /dev/null +++ b/apps/frontend/src/app/pages/UploadPage.test.tsx @@ -0,0 +1,41 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { describe, expect, it, vi } from 'vitest'; +import { UploadPage } from './UploadPage'; + +vi.mock('@/features/upload/hooks/useUpload', () => ({ + ACCEPTED_ARCHIVE_TYPES: { 'application/zip': ['.zip'] }, + MAX_FILE_SIZE: 1024, + useUpload: () => ({ + selectFile: vi.fn(), rejectFile: vi.fn(), retry: vi.fn(), source: 'upload', + uploadFile: null, error: null, loading: false, analyseFile: vi.fn(), + removeFile: vi.fn(), + }), +})); + +vi.mock('@/features/upload/hooks/useGitHubImport', () => ({ + useGitHubImport: () => ({ + retry: vi.fn(), source: 'github', githubUrl: '', setGithubUrl: vi.fn(), + previewName: null, error: null, loading: false, analyseGithub: vi.fn(), + }), +})); + +describe('UploadPage source tabs', () => { + it('keeps the selected tab and visible panel in sync', async () => { + render(); + + const fileTab = screen.getByRole('tab', { name: 'Upload File' }); + const githubTab = screen.getByRole('tab', { name: 'GitHub URL' }); + expect(fileTab).toHaveAttribute('aria-selected', 'true'); + expect(screen.getByRole('tabpanel')).toHaveAttribute('id', 'upload-file-panel'); + + fireEvent.click(githubTab); + + expect(githubTab).toHaveAttribute('aria-selected', 'true'); + expect(fileTab).toHaveAttribute('aria-selected', 'false'); + await waitFor(() => { + expect(screen.getByRole('tabpanel')).toHaveAttribute('id', 'upload-github-panel'); + }); + expect(screen.getByTestId('github-import-title')).toHaveTextContent('Import from GitHub'); + }); +}); diff --git a/apps/frontend/src/app/pages/UploadPage.tsx b/apps/frontend/src/app/pages/UploadPage.tsx index 81b38ad..7a9b9b1 100644 --- a/apps/frontend/src/app/pages/UploadPage.tsx +++ b/apps/frontend/src/app/pages/UploadPage.tsx @@ -49,7 +49,7 @@ export function UploadPage() { }; return ( -
+
-
+
-
Name