diff --git a/apps/frontend/src/app/pages/DependenciesPage.test.tsx b/apps/frontend/src/app/pages/DependenciesPage.test.tsx
index e895d53..7f4284b 100644
--- a/apps/frontend/src/app/pages/DependenciesPage.test.tsx
+++ b/apps/frontend/src/app/pages/DependenciesPage.test.tsx
@@ -1,14 +1,41 @@
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useDependencies } from '@/features/dependencies/hooks/useDependencies';
-import type { DependencyDiagnostic, DependencyGraphResponse } from '@/shared/services/api/types';
+import { architectureService } from '@/shared/services/api/architecture';
+import type { DependencyDiagnostic, DependencyGraphResponse, RevisionManifestResponse } from '@/shared/services/api/types';
import { DependenciesPage } from './DependenciesPage';
vi.mock('@/features/dependencies/hooks/useDependencies', () => ({
useDependencies: vi.fn(),
}));
+vi.mock('@/shared/services/api/architecture', () => ({
+ architectureService: { getRevisionManifest: vi.fn() },
+}));
+
+const revisionManifest: RevisionManifestResponse = {
+ manifest: {
+ schemaVersion: 'revision-manifest.v1',
+ repositoryId: 'repo-1',
+ revisionKind: 'upload',
+ revisionValue: `sha256:${'0'.repeat(64)}`,
+ revisionRef: null,
+ snapshotId: 'snap_example',
+ snapshotSchemaVersion: 'ri.v1',
+ extractors: [{ name: 'dependency-manifest', version: '1.2.0' }],
+ producerSetHash: `sha256:${'1'.repeat(64)}`,
+ configHash: `sha256:${'2'.repeat(64)}`,
+ canonicalGraphHash: `sha256:${'3'.repeat(64)}`,
+ createdAt: '2026-07-25T00:00:00Z',
+ sealedAt: '2026-07-25T00:00:05Z',
+ },
+ manifestDigest: `sha256:${'4'.repeat(64)}`,
+ verificationMethod: 'sha256-canonical-json',
+ verificationState: 'verified',
+ verificationNote: 'This digest is a SHA-256 over the canonical JSON encoding of the manifest fields above.',
+};
+
const graph: DependencyGraphResponse = {
schemaVersion: 'dependency-graph.v2',
repositoryId: 'repo-1',
@@ -124,6 +151,7 @@ function diagnostic(severity: DependencyDiagnostic['severity']): DependencyDiagn
describe('DependenciesPage', () => {
beforeEach(() => {
mockDependencies();
+ vi.mocked(architectureService.getRevisionManifest).mockResolvedValue(revisionManifest);
});
it('shows uncomputed assessments without clean badges or numeric fallbacks', () => {
@@ -136,14 +164,21 @@ describe('DependenciesPage', () => {
expect(screen.queryByText('outdated')).not.toBeInTheDocument();
});
- it('renders the sealed snapshot identity, consistent with Architecture/Review', () => {
+ it('keeps the sealed snapshot identity one click away instead of on the landing strip (#176)', async () => {
renderPage();
- expect(screen.getByText('Snapshot')).toBeInTheDocument();
+ await waitFor(() => expect(screen.getByTestId('revision-manifest')).toBeInTheDocument());
+ // Collapsed by default: the snapshot id stays visible, but the hashes do not.
expect(screen.getByText('snap_example')).toBeInTheDocument();
- expect(screen.getByText('Canonical graph hash')).toBeInTheDocument();
- expect(screen.getByText(graph.canonicalGraphHash)).toBeInTheDocument();
+ expect(screen.queryByText(revisionManifest.manifestDigest)).not.toBeInTheDocument();
+ expect(screen.queryByText(revisionManifest.manifest.canonicalGraphHash!)).not.toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('button', { name: /details/i }));
+
expect(screen.getByText('Manifest digest')).toBeInTheDocument();
+ expect(screen.getByText(revisionManifest.manifestDigest)).toBeInTheDocument();
+ expect(screen.getByText('Canonical graph hash')).toBeInTheDocument();
+ expect(screen.getByText(revisionManifest.manifest.canonicalGraphHash!)).toBeInTheDocument();
});
it('shows an empty inventory instead of a search-miss message', () => {
diff --git a/apps/frontend/src/app/pages/DependenciesPage.tsx b/apps/frontend/src/app/pages/DependenciesPage.tsx
index 63289b1..80036fb 100644
--- a/apps/frontend/src/app/pages/DependenciesPage.tsx
+++ b/apps/frontend/src/app/pages/DependenciesPage.tsx
@@ -6,6 +6,7 @@ import { EmptyState } from '@/shared/components/ui/EmptyState';
import { DataSourceBadge } from '@/shared/components/ui/DataSourceBadge';
import { ExportMenu } from '@/shared/components/ui/ExportMenu';
import { useDependencies } from '@/features/dependencies/hooks/useDependencies';
+import { RevisionManifestPanel } from '@/features/architecture/components/RevisionManifestPanel';
import type { DependencyAssessment } from '@/shared/services/api/types';
export function DependenciesPage() {
@@ -121,12 +122,9 @@ export function DependenciesPage() {
-
+
+
+
@@ -227,12 +225,3 @@ function Stat({ label, value }: { label: string; value: number | string }) {
);
}
-
-function Identity({ label, value }: { label: string; value: string }) {
- return (
-
- );
-}
diff --git a/apps/frontend/src/app/pages/EngineeringReviewPage.test.tsx b/apps/frontend/src/app/pages/EngineeringReviewPage.test.tsx
new file mode 100644
index 0000000..6043104
--- /dev/null
+++ b/apps/frontend/src/app/pages/EngineeringReviewPage.test.tsx
@@ -0,0 +1,134 @@
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { useReview } from '@/features/review/hooks/useReview';
+import { useReviewStore } from '@/features/review/store';
+import { architectureService } from '@/shared/services/api/architecture';
+import type { EngineeringReview } from '@/shared/types/review';
+import type { RevisionManifestResponse } from '@/shared/services/api/types';
+import { EngineeringReviewPage } from './EngineeringReviewPage';
+
+vi.mock('@/features/review/hooks/useReview', () => ({
+ useReview: vi.fn(),
+}));
+
+vi.mock('@/shared/services/api/architecture', () => ({
+ architectureService: { getRevisionManifest: vi.fn() },
+}));
+
+const review: EngineeringReview = {
+ schemaVersion: 'engineering-review.v2',
+ repositoryId: 'repo-1',
+ repositoryName: 'sample',
+ revisionKind: 'upload',
+ revisionValue: `sha256:${'0'.repeat(64)}`,
+ snapshotId: 'snap_example',
+ snapshotSchemaVersion: 'ri.v1',
+ canonicalGraphHash: `sha256:${'1'.repeat(64)}`,
+ manifestDigest: `sha256:${'2'.repeat(64)}`,
+ provenance: {
+ source: 'ri.v1',
+ snapshotId: 'snap_example',
+ snapshotSchemaVersion: 'ri.v1',
+ canonicalGraphHash: `sha256:${'1'.repeat(64)}`,
+ },
+ generatedAt: '2026-07-25T00:00:00Z',
+ assessmentStatus: 'assessed',
+ categories: [],
+ findings: [],
+ summary: {
+ message: 'No findings were surfaced for this snapshot.',
+ findingsBySeverity: { info: 0, low: 0, medium: 0, high: 0, critical: 0 },
+ assessedCategories: 8,
+ partiallyAssessedCategories: 0,
+ notAssessedCategories: 0,
+ insufficientEvidenceCategories: 0,
+ evidenceBackedFindingCount: 0,
+ fileScopedFindingCount: 0,
+ omittedUnsupportedDiagnosticCount: 0,
+ vulnerabilityScanning: 'not_assessed',
+ },
+};
+
+const revisionManifest: RevisionManifestResponse = {
+ manifest: {
+ schemaVersion: 'revision-manifest.v1',
+ repositoryId: 'repo-1',
+ revisionKind: 'upload',
+ revisionValue: `sha256:${'0'.repeat(64)}`,
+ revisionRef: null,
+ snapshotId: 'snap_example',
+ snapshotSchemaVersion: 'ri.v1',
+ extractors: [{ name: 'typescript-extractor', version: '1.0.0' }],
+ producerSetHash: `sha256:${'1'.repeat(64)}`,
+ configHash: `sha256:${'2'.repeat(64)}`,
+ canonicalGraphHash: `sha256:${'3'.repeat(64)}`,
+ createdAt: '2026-07-25T00:00:00Z',
+ sealedAt: '2026-07-25T00:00:05Z',
+ },
+ manifestDigest: `sha256:${'4'.repeat(64)}`,
+ verificationMethod: 'sha256-canonical-json',
+ verificationState: 'verified',
+ verificationNote: 'This digest is a SHA-256 over the canonical JSON encoding of the manifest fields above.',
+};
+
+function renderPage() {
+ return render(
+
+
+ ,
+ );
+}
+
+describe('EngineeringReviewPage', () => {
+ beforeEach(() => {
+ vi.mocked(useReview).mockReturnValue({
+ review,
+ data: review,
+ source: 'upload',
+ status: 'success',
+ loading: false,
+ error: null,
+ empty: false,
+ success: true,
+ retry: vi.fn(),
+ refresh: vi.fn(),
+ activeRepository: null,
+ completedRepositories: [],
+ emptyReason: null,
+ });
+ useReviewStore.setState({
+ review,
+ selectedFindingId: null,
+ filterCategory: 'all',
+ filterSeverity: 'all',
+ filterDiagnosticCode: null,
+ });
+ vi.mocked(architectureService.getRevisionManifest).mockResolvedValue(revisionManifest);
+ });
+
+ it('keeps the sealed snapshot identity one click away instead of on the landing strip (#176)', async () => {
+ renderPage();
+
+ await waitFor(() => expect(screen.getByTestId('revision-manifest')).toBeInTheDocument());
+ expect(screen.getByText('snap_example')).toBeInTheDocument();
+ expect(screen.queryByText(revisionManifest.manifestDigest)).not.toBeInTheDocument();
+ expect(screen.queryByText(revisionManifest.manifest.canonicalGraphHash!)).not.toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole('button', { name: /details/i }));
+
+ expect(screen.getByText('Manifest digest')).toBeInTheDocument();
+ expect(screen.getByText(revisionManifest.manifestDigest)).toBeInTheDocument();
+ expect(screen.getByText(revisionManifest.manifest.canonicalGraphHash!)).toBeInTheDocument();
+ });
+
+ it('still renders the evidence-backed summary and assessment matrix', () => {
+ renderPage();
+
+ expect(screen.getByText('Evidence-backed summary')).toBeInTheDocument();
+ expect(screen.getByText('No findings were surfaced for this snapshot.')).toBeInTheDocument();
+ expect(
+ screen.getByText('No overall score, grade, health percentage, or category score is produced.'),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/apps/frontend/src/app/pages/EngineeringReviewPage.tsx b/apps/frontend/src/app/pages/EngineeringReviewPage.tsx
index 8836652..d20e9d9 100644
--- a/apps/frontend/src/app/pages/EngineeringReviewPage.tsx
+++ b/apps/frontend/src/app/pages/EngineeringReviewPage.tsx
@@ -10,6 +10,7 @@ import { FindingCard } from '@/features/review/components/FindingCard';
import { FindingDetail } from '@/features/review/components/FindingDetail';
import { ReviewFilters } from '@/features/review/components/ReviewFilters';
import { ExportMenu } from '@/shared/components/ui/ExportMenu';
+import { RevisionManifestPanel } from '@/features/architecture/components/RevisionManifestPanel';
import type { ReviewAssessmentState, ReviewSeverity } from '@/shared/types/review';
const STATE_LABEL: Record = {
@@ -102,12 +103,9 @@ export function EngineeringReviewPage() {
categories without sufficient evidence are marked Not assessed.
-
+
+
+
@@ -207,15 +205,6 @@ export function EngineeringReviewPage() {
);
}
-function Identity({ label, value }: { label: string; value: string }) {
- return (
-
- );
-}
-
function StatusPanel({ text }: { text: string }) {
return (
diff --git a/apps/frontend/src/app/pages/RepositoryDetailPage.test.tsx b/apps/frontend/src/app/pages/RepositoryDetailPage.test.tsx
new file mode 100644
index 0000000..5e0d61f
--- /dev/null
+++ b/apps/frontend/src/app/pages/RepositoryDetailPage.test.tsx
@@ -0,0 +1,77 @@
+import { render, screen } from '@testing-library/react';
+import { MemoryRouter, Route, Routes } from 'react-router-dom';
+import { describe, expect, it, vi } from 'vitest';
+import type { Repository } from '@/shared/types';
+import { RepositoryDetailPage } from './RepositoryDetailPage';
+
+const repositoryState = vi.hoisted(() => ({ repositories: [] as Repository[] }));
+
+vi.mock('@/features/repositories/hooks/useRepository', () => ({
+ useRepository: () => ({ repositories: repositoryState.repositories }),
+}));
+
+vi.mock('@/features/repositories/components/RepositoryOutcomeSummary', () => ({
+ RepositoryOutcomeSummary: ({ repository }: { repository: Repository }) => (
+
Outcome summary for {repository.name}
+ ),
+}));
+
+const completedRepository: Repository = {
+ id: 'repo-1',
+ name: 'sample',
+ source: 'upload',
+ size: 10,
+ fileCount: 5,
+ status: 'completed',
+ analysisStage: 'completed',
+ analysisProgress: 100,
+ uploadedAt: '2026-07-22T08:00:00Z',
+ meta: {
+ language: 'TypeScript',
+ framework: 'React',
+ totalFiles: 5,
+ totalFolders: 2,
+ entryPoint: 'src/main.tsx',
+ configFiles: [],
+ packageManager: 'npm',
+ hasReadme: true,
+ hasLicense: true,
+ licenseName: 'MIT',
+ },
+ fileTree: [],
+};
+
+function renderPage(repositoryId = 'repo-1') {
+ return render(
+
+
+ } />
+
+ ,
+ );
+}
+
+describe('RepositoryDetailPage', () => {
+ it('leads the completed repository landing view with the outcome summary, before the tabs (#176)', () => {
+ repositoryState.repositories = [completedRepository];
+
+ renderPage();
+
+ const summary = screen.getByTestId('outcome-summary-stub');
+ expect(summary).toHaveTextContent('Outcome summary for sample');
+
+ const overviewTab = screen.getByRole('button', { name: 'Overview' });
+ expect(summary.compareDocumentPosition(overviewTab) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ });
+
+ it('does not show the outcome summary for a repository that failed analysis', () => {
+ repositoryState.repositories = [
+ { ...completedRepository, status: 'error', errorMessage: 'Extraction crashed.', meta: null },
+ ];
+
+ renderPage();
+
+ expect(screen.queryByTestId('outcome-summary-stub')).not.toBeInTheDocument();
+ expect(screen.getByText('Extraction crashed.')).toBeInTheDocument();
+ });
+});
diff --git a/apps/frontend/src/app/pages/RepositoryDetailPage.tsx b/apps/frontend/src/app/pages/RepositoryDetailPage.tsx
index f7e6fc3..a7c3183 100644
--- a/apps/frontend/src/app/pages/RepositoryDetailPage.tsx
+++ b/apps/frontend/src/app/pages/RepositoryDetailPage.tsx
@@ -22,6 +22,7 @@ import { EmptyState } from '@/shared/components/ui/EmptyState';
import { Badge } from '@/shared/components/ui/Badge';
import { DataSourceBadge } from '@/shared/components/ui/DataSourceBadge';
import { RepositoryExplorer } from '@/features/explorer/components/RepositoryExplorer';
+import { RepositoryOutcomeSummary } from '@/features/repositories/components/RepositoryOutcomeSummary';
import { useRepositoryDetail } from '@/features/repositories/hooks/useRepositoryDetail';
import { useRepositoryTree } from '@/features/repositories/hooks/useRepositoryTree';
import { repositoryStatusVariant } from '@/features/repositories/status';
@@ -85,6 +86,8 @@ export function RepositoryDetailPage() {
) : repo.status === 'completed' && repo.meta ? (
<>
+
+
{tabs.map((tab) => (