diff --git a/.claude/metrics/review-metrics.jsonl b/.claude/metrics/review-metrics.jsonl
index afcc9df8b..bf57a41b8 100644
--- a/.claude/metrics/review-metrics.jsonl
+++ b/.claude/metrics/review-metrics.jsonl
@@ -64,3 +64,4 @@
{"pr":400,"story":"#391","agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"timestamp":"2026-03-03T00:01:00Z"}
{"pr":400,"story":"#391","agent":"ux-designer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"timestamp":"2026-03-03T00:01:00Z"}
{"pr":401,"story":"#392","epic":"#384","timestamp":"2026-03-03T10:00:00Z","reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":3,"low":0,"informational":0}},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":2}},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":2,"medium":0,"low":0,"informational":0}},{"agent":"ux-designer","verdict":"approve","findings":{"critical":0,"high":0,"medium":5,"low":3,"informational":0}}],"fixLoops":1,"retries":2}
+{"pr":404,"issues":[359],"epic":4,"type":"feat","mergedAt":"2026-03-03T11:15:00Z","filesChanged":2,"linesChanged":94,"fixLoopCount":0,"reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1}],"totalFindings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0}}
diff --git a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx
index d41d0f7d8..e42d132d9 100644
--- a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx
+++ b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.test.tsx
@@ -130,6 +130,22 @@ jest.unstable_mockModule('../../lib/householdItemSubsidiesApi.js', () => ({
fetchHouseholdItemSubsidyPayback: mockFetchHouseholdItemSubsidyPayback,
}));
+// Mock LinkedDocumentsSection to avoid pulling in full documents component tree
+jest.unstable_mockModule('../../components/documents/LinkedDocumentsSection.js', () => ({
+ LinkedDocumentsSection: function MockLinkedDocumentsSection(props: {
+ entityType: string;
+ entityId: string;
+ }) {
+ return (
+
+ Documents
+ {props.entityType}
+ {props.entityId}
+
+ );
+ },
+}));
+
// Helper to capture current location
function LocationDisplay() {
const location = useLocation();
@@ -1063,4 +1079,78 @@ describe('HouseholdItemDetailPage', () => {
expect(screen.getAllByText('Delivered').length).toBeGreaterThan(0);
});
});
+
+ describe('Documents section', () => {
+ it('renders LinkedDocumentsSection with entityType="household_item"', async () => {
+ mockGetHouseholdItem.mockResolvedValue(makeItem());
+
+ renderPage();
+
+ await waitFor(() => {
+ expect(screen.getByRole('heading', { name: 'Standing Desk' })).toBeInTheDocument();
+ });
+
+ expect(screen.getByTestId('linked-documents-section')).toBeInTheDocument();
+ expect(screen.getByTestId('entity-type')).toHaveTextContent('household_item');
+ });
+
+ it('renders with correct entityId from URL params', async () => {
+ mockGetHouseholdItem.mockResolvedValue(makeItem({ id: 'item-abc' }));
+
+ renderPage('item-abc');
+
+ await waitFor(() => {
+ expect(screen.getByRole('heading', { name: 'Standing Desk' })).toBeInTheDocument();
+ });
+
+ expect(screen.getByTestId('entity-id')).toHaveTextContent('item-abc');
+ });
+
+ it('renders Documents section heading between Subsidies and Metadata sections', async () => {
+ mockGetHouseholdItem.mockResolvedValue(makeItem());
+
+ renderPage();
+
+ await waitFor(() => {
+ expect(screen.getByRole('heading', { name: 'Standing Desk' })).toBeInTheDocument();
+ });
+
+ // Get all h2 headings on the page
+ const headings = screen.getAllByRole('heading', { level: 2 });
+ const headingTexts = headings.map((h) => h.textContent);
+
+ // Verify "Documents" heading exists
+ expect(headingTexts).toContain('Documents');
+
+ // Verify Documents comes after Subsidies
+ const subsidiesIndex = headingTexts.findIndex((text) => text === 'Subsidies');
+ const documentsIndex = headingTexts.findIndex((text) => text === 'Documents');
+ expect(subsidiesIndex).toBeGreaterThan(-1);
+ expect(documentsIndex).toBeGreaterThan(-1);
+ expect(documentsIndex).toBeGreaterThan(subsidiesIndex);
+ });
+
+ it('does not render LinkedDocumentsSection in loading state', async () => {
+ mockGetHouseholdItem.mockImplementation(() => new Promise(() => {})); // Never resolves
+
+ renderPage();
+
+ expect(screen.getByText('Loading household item...')).toBeInTheDocument();
+ expect(screen.queryByTestId('linked-documents-section')).not.toBeInTheDocument();
+ });
+
+ it('does not render LinkedDocumentsSection when item returns 404', async () => {
+ mockGetHouseholdItem.mockRejectedValue(
+ new MockApiClientError(404, { code: 'NOT_FOUND', message: 'Item not found' }),
+ );
+
+ renderPage();
+
+ await waitFor(() => {
+ expect(screen.getByText('Item Not Found')).toBeInTheDocument();
+ });
+
+ expect(screen.queryByTestId('linked-documents-section')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx
index 1828177a2..399f7d422 100644
--- a/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx
+++ b/client/src/pages/HouseholdItemDetailPage/HouseholdItemDetailPage.tsx
@@ -46,6 +46,7 @@ import { formatDate, formatCurrency } from '../../lib/formatters.js';
import { HouseholdItemStatusBadge } from '../../components/HouseholdItemStatusBadge/HouseholdItemStatusBadge.js';
import { StatusBadge } from '../../components/StatusBadge/StatusBadge.js';
import { useToast } from '../../components/Toast/ToastContext.js';
+import { LinkedDocumentsSection } from '../../components/documents/LinkedDocumentsSection.js';
import styles from './HouseholdItemDetailPage.module.css';
const CATEGORY_LABELS: Record = {
@@ -1146,6 +1147,9 @@ export function HouseholdItemDetailPage() {
)}
+ {/* Documents section */}
+
+
{/* Metadata card */}