feat(household-items): add document linking to household item detail page#404
Conversation
… detail page Add LinkedDocumentsSection component to the household item detail page between the Subsidies and Metadata sections. This enables users to link Paperless-ngx documents to household items. Fixes #359 Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Add integration tests verifying LinkedDocumentsSection is correctly mounted in HouseholdItemDetailPage with proper entityType and entityId props. Tests cover: - Renders LinkedDocumentsSection with household_item entity type - Passes correct entityId from URL params - Documents section positioned correctly in page structure - Component not rendered in loading and error states Co-Authored-By: Claude qa-integration-tester (Haiku 4.5) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
steilerDev
left a comment
There was a problem hiding this comment.
[security-engineer] Security review for PR #404 — Story #359 (Document Linking for Household Items)
Review Summary
This PR integrates the existing LinkedDocumentsSection component into HouseholdItemDetailPage. The component was previously audited in the EPIC-08 stories (PRs #362–#382) and carries a strong security posture. This integration adds no new attack surface — it reuses existing, already-reviewed code paths with a new, statically typed entityType literal.
Security Checklist
- No SQL/command/XSS injection vectors in new code
- Authentication/authorization enforced on all new endpoints
- No sensitive data (secrets, tokens, PII) exposed in logs, errors, or client responses
- User input validated and sanitized at API boundaries
- No new dependencies introduced
- No hardcoded credentials or secrets
- CORS configuration unchanged
- Error responses do not leak internal details
Findings
XSS — No Risk (Confirmed)
The entityType="household_item" prop is a hardcoded string literal, not user-controlled data. The entityId={id!} value comes from React Router's useParams, which extracts a path segment from the URL. This value is:
- Passed as a query parameter (
?entityId=...) to the backend throughlistDocumentLinks - Validated server-side in
documentLinks.tswithminLength: 1, maxLength: 36and anenumcheck onentityType - Used exclusively in Drizzle ORM parameterized queries in
documentLinkService - Never rendered via
dangerouslySetInnerHTML,innerHTML, oreval
No XSS vector exists in this integration path.
Input Validation — Strong
The server-side schema at server/src/routes/documentLinks.ts lines 24 and 37 enumerates exactly the three permitted entityType values (work_item, household_item, invoice) with additionalProperties: false. The entityId is bounded to maxLength: 36. The LinkedDocumentsSectionProps interface types entityType as DocumentLinkEntityType (a union of the same three string literals), so TypeScript enforces the constraint at the call site. The prop passed is a string literal, satisfying both layers.
Auth — Confirmed Enforced
All three document-links endpoints (POST /api/document-links, GET /api/document-links, DELETE /api/document-links/:id) include an explicit if (!request.user) throw new UnauthorizedError() guard as verified in server/src/routes/documentLinks.ts at lines 70, 99, and 125. No auth gap is introduced by this frontend integration.
Non-Null Assertion id! — Acceptable
The id! pattern on line 1151 of HouseholdItemDetailPage.tsx is consistent with the established pattern throughout this component (the same id from useParams is used at line 96 and referenced throughout the file). The page is only rendered on the /household-items/:id route, so id will always be present when this component renders. This is the same pattern used for work item and invoice detail pages.
OIDC/Session — Unchanged
No changes to authentication or session handling. The component calls the same apiClient functions that carry the existing session cookie on all requests.
Test Coverage — Adequate for Security Purposes
The test mock correctly captures entityType and entityId as rendered props. The five test cases cover: correct entityType value, correct entityId from URL params, section ordering, no-render in loading state, and no-render in 404 state. The tests confirm entityType is exactly "household_item" (not a wrong or empty string), which validates the integration contract.
Conclusion
No security issues identified. The integration correctly reuses a previously audited component with a statically typed entityType literal and an entityId routed through the existing validated, auth-guarded document-links API surface.
steilerDev
left a comment
There was a problem hiding this comment.
[product-architect]
Architecture Review: Story 4.8 — Document Linking for Household Items
Verdict: Approve (posting as comment; cannot approve own-org PRs)
This PR correctly integrates the existing LinkedDocumentsSection component into the household item detail page. The change is minimal and well-scoped.
What Was Verified
Architecture compliance -- Follows the exact pattern established by Story 8.4 (work items, PR #372) and Story 8.5 (invoices, PR #378):
- Named import with
.jsextension (ESM convention) entityType="household_item"matches thedocument_linksschema CHECK constraint (entity_type IN ('work_item', 'household_item', 'invoice')) and the Fastify route JSON Schema enumentityId={id!}uses the non-null assertion consistent withWorkItemDetailPageandInvoiceDetailPage- Component rendered directly without a wrapper div, consistent with
InvoiceDetailPage
Schema consistency -- No schema changes needed. Migration 0009 already includes household_item in the entity_type enum. The document_links table, service layer, and API routes all support this entity type.
API contract adherence -- The API contract (wiki) already documents household_item as a valid entityType for GET/POST/DELETE /api/document-links. No contract updates required.
Component placement -- Documents section is positioned between Subsidies and Metadata sections, which is the logical location (content sections before metadata/footer).
Test coverage -- 5 tests covering:
- Correct
entityType="household_item"prop - Correct
entityIdfrom URL params - Section ordering (Documents after Subsidies)
- Component excluded during loading state
- Component excluded on 404 error
The mock pattern (function component with data-testid attributes) matches the approach used in InvoiceDetailPage.test.tsx.
CI status -- Quality Gates, Docker, and E2E Smoke Tests all pass.
No Issues Found
This is a clean, minimal integration that reuses existing infrastructure exactly as designed. The LinkedDocumentsSection component is fully self-contained (renders its own <section>, manages its own state via useDocumentLinks hook), so no additional state management or API wiring is needed in the host page.
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner] Acceptance criteria review for Story #359 (Document Linking for Household Items).
Verdict: APPROVED
All 11 acceptance criteria are met.
Acceptance Criteria Checklist
| AC | Description | Result |
|---|---|---|
| 1 | Documents section showing linked Paperless-ngx documents | PASS — LinkedDocumentsSection renders the section with full document display |
| 2 | Linked document displays thumbnail, title, date, tags | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 3 | "Add Document" button opens document browser modal | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 4 | Link created via API, document appears without reload | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 5 | Unlink action with confirmation | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 6 | Click thumbnail/title opens preview/detail | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 7 | "View in Paperless-ngx" opens in new tab | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 8 | Empty state with prompt for receipts/warranties/manuals | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 9 | Not-configured message when Paperless unavailable | PASS — handled by LinkedDocumentsSection (verified in Stories 8.4/8.5) |
| 10 | Reuses same component from Stories 8.4/8.5 with entityType="household_item" |
PASS — imports LinkedDocumentsSection and passes entityType="household_item" and entityId={id!} |
| 11 | Deleting household item cascades document link deletion | PASS — verified in householdItemService.ts line 525: deleteLinksForEntity(db, 'household_item', id) (implemented in Story 4.1) |
Agent Attribution Verification
- Production code commit:
Co-Authored-By: Claude frontend-developer (Haiku 4.5)-- correct - Test commit:
Co-Authored-By: Claude qa-integration-tester (Haiku 4.5)-- correct
Scope Assessment
Minimal and well-scoped. Only 2 files modified with 4 lines of production code (1 import + 1 comment + 1 component render + 1 blank line). The story is intentionally a thin integration point -- all document linking behavior is encapsulated in the existing LinkedDocumentsSection component, and AC #10 explicitly requires this reuse pattern.
CI Status
- Quality Gates: PASS
- E2E Smoke Tests: PASS
- Docker: PASS
Test Coverage
5 integration tests covering: correct entityType prop, correct entityId from URL params, section ordering (between Subsidies and Metadata), and non-rendering in loading/error states. Tests use a mock of LinkedDocumentsSection which is appropriate since the component itself has its own comprehensive test suite.
Notes
- Unable to use
--approveaction because PR was created by the same GitHub account. This comment serves as the formal PO approval. - No other agent reviews are posted yet, but the change is extremely low risk (4 lines of production code, all reusing verified components).
- The
Fixes #359footer in the commit will auto-close the issue when merged tomain.
|
🎉 This PR is included in version 1.12.0-beta.9 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.12.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Add
LinkedDocumentsSectioncomponent to the household item detail page. This enables users to link Paperless-ngx documents (receipts, warranties, manuals, etc.) to household items, maintaining parity with work item document linking (Story #360).The component is placed between the Subsidies and Metadata sections of the detail page.
Changes
LinkedDocumentsSectionfromclient/src/components/documents/LinkedDocumentsSection.jsentityType="household_item"andentityId={id!}propsTest Coverage
Acceptance Criteria
.jsextension (named import, not default)entityType="household_item"(not"work_item")entityId={id!}usesidfromuseParamsid!consistent with existing patternFixes #359
🤖 Generated with Claude Code