feat(household-items): Household Items List Page (Story 4.3) #389#398
feat(household-items): Household Items List Page (Story 4.3) #389#398steilerDev merged 6 commits intobetafrom
Conversation
…filtering, sorting, and pagination
Implements Story 4.3 (Household Items List Page) with:
- New householdItemsApi.ts with CRUD functions and list queries
- HouseholdItemStatusBadge component for purchase status badges
- Design tokens for household item status colors (light & dark modes)
- Fully functional HouseholdItemsPage with:
- Search input with debouncing
- Multi-filter controls (category, status, room, vendor)
- Sortable columns (name, category, status, room, order_date, expected_delivery_date, created_at, updated_at)
- Desktop table view with pagination
- Mobile card view (responsive)
- Delete confirmation modal with inline deletion
- Keyboard shortcuts (n=new, /=search, arrows=select, Enter=open, ?=help, Esc=cancel)
- Empty states for no items and no matches
- Stub pages for HouseholdItemCreatePage and HouseholdItemDetailPage
- Updated App.tsx with three new routes: household-items/{new,/:id}
Design tokens added to tokens.css:
- --color-hi-status-not-ordered: gray
- --color-hi-status-ordered: blue
- --color-hi-status-in-transit: amber
- --color-hi-status-delivered: green
Responsive CSS matches WorkItemsPage patterns:
- Desktop: full table view
- Tablet: adapted filter layout
- Mobile: card view, full-width buttons
All TypeScript types properly imported from @cornerstone/shared.
No hardcoded colors in CSS—all use design tokens.
Fixes #389
Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…utes The stub pages for HouseholdItemCreatePage and HouseholdItemDetailPage were created but not included in the previous commit. App.tsx references these via lazy imports, so the build would fail without them. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
Implement comprehensive test coverage for household items list page: - householdItemsApi.test.ts: 27 tests for list, get, create, update, delete API functions - HouseholdItemStatusBadge.test.tsx: 10 tests for status badge component rendering - HouseholdItemsPage.test.tsx: 16 tests for page structure, filters, pagination, and error handling - Update App.test.tsx with mocks for household items and vendors APIs Total: 53 tests passing, covering acceptance criteria for listing, filtering, searching, pagination, and error handling of household items. Co-Authored-By: Claude qa-integration-tester (Haiku 4.5) <noreply@anthropic.com>
The test found multiple headings matching "household items" — the h1 page title and the h2 empty state message. Adding level: 1 constrains the query to match only the page title heading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
## Changes - **Design tokens**: Add amber palette (amber-100, amber-300, amber-800) to Layer 1 for household item in-transit status badge; update in-transit tokens in Layer 2 and dark mode to reference palette tokens instead of hardcoded hex values - **CSS token migration**: Replace ALL hardcoded spacing, font-size, border-radius, transition, and z-index values with semantic design tokens in: - HouseholdItemsPage.module.css (major refactoring) - HouseholdItemStatusBadge.module.css (badge padding) - HouseholdItemCreatePage.module.css (stub page) - HouseholdItemDetailPage.module.css (stub page) - **Accessibility enhancements**: - Add :focus-visible to all interactive elements (buttons, inputs, selects) - Add prefers-reduced-motion guard for all transitions - Add keyboard support (Enter/Space) to sortable table headers - Add aria-sort attribute to table headers - Update action menu aria-labels to include item name - Add role="menu" and role="menuitem" to dropdown menus - **Responsive improvements**: - Update tablet breakpoint from 1024px to 1023px - Hide Room and Vendor columns (4th and 7th) on tablets for space - Apply token-based spacing to mobile media query - **Content fix**: - Rename "Cost" column to "Planned Cost" for clarity - Update mobile card label from "Cost:" to "Planned Cost:" - **CSS fixes**: - Fix secondaryButton hover to use var(--color-bg-hover) instead of var(--color-border) - Fix card hover shadow from var(--shadow-md) to var(--shadow-lg) Verification: All hardcoded hex values removed from tokens.css. All hardcoded spacing/sizing values in HouseholdItemsPage replaced with tokens. Zero hex color values in CSS Module files (except dark palette in tokens.css, which was pre-existing). Co-Authored-By: Claude frontend-developer (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 of PR #398 — Story 4.3: Household Items List Page (frontend-only).
Review Summary
No security issues found. This is a clean frontend-only PR.
Checklist
- No SQL/command/XSS injection vectors in new code
- Authentication/authorization enforced — all API calls use session cookies via the shared
apiClient.ts; no auth bypass possible at the frontend layer - No sensitive data exposed in logs, errors, or client responses
- User input validated and sanitized at API boundaries —
URLSearchParams.set()handles encoding; filter values are TypeScript-constrained enum types from@cornerstone/shared - New dependencies — none added
- No hardcoded credentials or secrets
- CORS configuration unchanged
- Error responses do not leak internal details
Detailed Analysis
XSS — No issues
Zero uses of dangerouslySetInnerHTML, innerHTML, or eval() anywhere in the diff. All dynamic content renders in JSX text nodes, which React escapes by default:
- Item names in table/cards:
{item.name}— safe text node - Modal confirm text:
"<strong>{deletingItem.name}</strong>"— JSX entities, safe - Vendor names:
{item.vendor?.name || '—'}— safe text node - Error messages from
ApiClientErrorshown in alert banner — same pattern as rest of app
CSS Class from Status Enum — Safe
styles[status] in HouseholdItemStatusBadge follows the established pattern used throughout the codebase (invoice status, work item status). CSS Modules scopes class names at build time, so there is no CSS injection path even if an unexpected status value were passed. The TypeScript type HouseholdItemStatus constrains the prop at compile time.
URL Field — Not Rendered in List
The household_items.url field (retailer URL stored for household items) is present in the API response type but is not rendered as a clickable <a> link in this list page. The field appears only in test fixture data and is not displayed in the page component. When the detail page (future story) renders this field, it must use rel="noopener noreferrer" and target="_blank" — this was already flagged as an informational finding in PR #396 review.
Navigate Calls — No Open Redirect
All navigate() calls use either hardcoded literal paths ('/household-items/new') or server-provided item.id values from the API response. No user-controlled string is passed directly to navigate().
URL Parameter Handling — Safe
parseInt(searchParams.get('page') || '1', 10) uses radix 10 correctly. The resulting page number flows to the API as a query parameter; the server validates it. URLSearchParams.set() handles percent-encoding for the search query and room filter inputs, preventing header injection or path traversal.
No Client-Side Storage of Sensitive Data
No tokens, credentials, or PII written to localStorage or sessionStorage.
Console Logging — Informational
console.error('Failed to load vendors:', err) at line 2123 of the diff logs a vendor load failure. The error object may contain API error details. This is consistent with the rest of the codebase and acceptable in a self-hosted single-tenant application — no finding raised.
Vendor Name in Filter Dropdown — Safe
{vendor.name} rendered as <option> text — JSX escapes this, no XSS risk.
steilerDev
left a comment
There was a problem hiding this comment.
[ux-designer]
Design compliance review for PR #398 — Story 4.3 Household Items List Page.
Summary
The core token architecture is sound: the new --color-hi-status-* token family is properly structured across all three layers (Layer 1 palette, Layer 2 semantic in :root, Layer 3 dark mode in [data-theme="dark"]), the HouseholdItemStatusBadge component module is clean (no hardcoded hex values, correct --radius-full for pill shape), and the responsive split at 768px (table/card) aligns with the spec. However, there are a substantial number of medium-severity issues — primarily pervasive hardcoded literal values instead of spacing, typography, radius, and transition tokens — plus several accessibility gaps that need addressing before this lands.
Critical / High
None.
Medium — Hardcoded Values Instead of Design Tokens
The entire HouseholdItemsPage.module.css file uses raw literal values for spacing, font sizes, font weights, border radii, and transition durations everywhere. The design system rule is explicit: every visual property must use a var(--token-name) reference. The following are representative violations (not exhaustive):
HouseholdItemsPage.module.css
| Property / Value | Required Token |
|---|---|
padding: 2rem (container, modal…) |
var(--spacing-8) |
margin-bottom: 2rem (header) |
var(--spacing-8) |
font-size: 2rem (pageTitle) |
var(--font-size-4xl) |
font-weight: 700 (pageTitle) |
var(--font-weight-bold) |
border-radius: 0.5rem (everywhere — filtersCard, tableContainer, card, modal, etc.) |
var(--radius-lg) |
border-radius: 0.25rem (menuButton) |
var(--radius-sm) |
border-radius: 0.375rem (paginationButton) |
var(--radius-md) |
font-size: 0.875rem (many locations) |
var(--font-size-sm) |
font-size: 0.75rem (filterLabel, table th) |
var(--font-size-xs) |
font-size: 1rem (loading, cardTitle) |
var(--font-size-base) |
font-size: 1.25rem (menuButton, modalTitle) |
var(--font-size-xl) |
font-size: 1.5rem (emptyState h2, mobile pageTitle) |
var(--font-size-2xl) |
font-weight: 500 (many locations) |
var(--font-weight-medium) |
font-weight: 600 (many locations) |
var(--font-weight-semibold) |
padding: 1.5rem (filtersCard, modalContent) |
var(--spacing-6) |
padding: 1rem (card, errorBanner, table td) |
var(--spacing-4) |
padding: 0.75rem 1rem (table th, pagination) |
var(--spacing-3) var(--spacing-4) |
padding: 0.625rem 1.25rem (primaryButton) |
var(--spacing-2-5) var(--spacing-5) |
padding: 0.5rem 0.75rem (filterSelect, secondary) |
var(--spacing-2) var(--spacing-3) |
padding: 0.25rem 0.5rem (menuButton) |
var(--spacing-1) var(--spacing-2) |
gap: 1rem (filtersRow, cardsContainer) |
var(--spacing-4) |
gap: 0.375rem (tagsCell) |
var(--spacing-1-5) |
gap: 0.5rem (cardBody, cardRow) |
var(--spacing-2) |
gap: 0.25rem (paginationPages) |
var(--spacing-1) |
gap: 0.75rem (modalActions) |
var(--spacing-3) |
gap: 0.25rem (filter column gap) |
var(--spacing-1) |
margin-top: 0.25rem (menuDropdown) |
var(--spacing-1) |
margin-bottom: 0.75rem (cardHeader) |
var(--spacing-3) |
transition: background-color 0.2s |
var(--transition-medium) or var(--transition-button) |
transition: color 0.2s |
var(--transition-medium) |
transition: border-color 0.2s |
var(--transition-input) |
transition: box-shadow 0.2s |
var(--transition-medium) |
z-index: 1000 (modal) |
var(--z-modal) |
z-index: 10 (menuDropdown) |
var(--z-dropdown) |
min-width: 120px (menuDropdown) |
can remain literal; no spacing token covers this |
The HouseholdItemCreatePage.module.css and HouseholdItemDetailPage.module.css (stub pages) also use padding: 2rem, font-size: 2rem, font-weight: 700, font-size: 1rem. These are low-footprint stubs but still need to use tokens.
Important pattern note: These buttons (primaryButton, secondaryButton, cancelButton, confirmDeleteButton) are fully duplicating patterns already in client/src/styles/shared.module.css as btnPrimary, btnSecondary, btnConfirmDelete. The local duplicates should be removed and replaced with composes: btnPrimary from '../../styles/shared.module.css' (or imported via shared.btnPrimary in JSX). Similarly, the modal, modalBackdrop, modalContent, modalActions, loading, and emptyState classes are all duplicates of their counterparts in shared.module.css.
Medium — Dark Mode: --color-hi-status-in-transit-* Tokens Use Hardcoded Hex
client/src/styles/tokens.css, lines 221–222 (Layer 2 :root):
--color-hi-status-in-transit-bg: #fef3c7;
--color-hi-status-in-transit-text: #92400e;These should reference Layer 1 palette tokens, not raw hex. The Layer 1 already contains amber-7 values for calendar items:
#fef3c7=--calendar-item-7-bg(amber-100-equivalent). This should instead be a proper amber palette token. The correct fix is to add amber palette tokens to Layer 1:
/* Layer 1 — add to :root */
--color-amber-100: #fef3c7;
--color-amber-800: #92400e;Then in Layer 2:
--color-hi-status-in-transit-bg: var(--color-amber-100);
--color-hi-status-in-transit-text: var(--color-amber-800);Dark mode Layer 3 ([data-theme="dark"], line 551):
--color-hi-status-in-transit-text: #fcd34d;This is also a hardcoded hex. The value #fcd34d is already present as --calendar-item-7-text. Add an amber palette token:
/* Layer 1 */
--color-amber-300: #fcd34d;Then in Layer 3:
--color-hi-status-in-transit-text: var(--color-amber-300);This matters because every other token in the HI status family references palette tokens or semantic tokens — the in-transit pair is the only one with hardcoded hex, which would be invisible to the token audit command.
Medium — Missing prefers-reduced-motion Guard
HouseholdItemsPage.module.css has numerous transition: declarations (on .primaryButton, .secondaryButton, .sortableHeader, .tableRow, .menuButton, .card, .paginationButton, etc.). None of them are wrapped in a prefers-reduced-motion media query guard. Per the design system principle of progressive enhancement:
@media (prefers-reduced-motion: reduce) {
.primaryButton,
.secondaryButton,
.tableRow,
.sortableHeader,
.menuButton,
.menuItem,
.card,
.paginationButton,
.cancelButton,
.confirmDeleteButton {
transition: none;
}
}Medium — Missing focus-visible on Interactive Elements
The following interactive elements in HouseholdItemsPage.module.css have no :focus-visible rule:
.primaryButton— should havebox-shadow: var(--shadow-focus).secondaryButton— should havebox-shadow: var(--shadow-focus-subtle).menuButton— should havebox-shadow: var(--shadow-focus-subtle).menuItem— should havebox-shadow: var(--shadow-focus-subtle)oroutline.cancelButton— should havebox-shadow: var(--shadow-focus-subtle).confirmDeleteButton— should havebox-shadow: var(--shadow-focus-danger).paginationButton— should havebox-shadow: var(--shadow-focus).filterSelect/.searchInput— these use:focus(not:focus-visible) andborder-color: var(--color-primary-hover)instead ofvar(--color-border-focus). The correct token for focused border is--color-border-focus.
All interactive elements must show a visible focus ring when navigated by keyboard. This is a keyboard accessibility requirement (WCAG 2.4.7 — Focus Visible).
Medium — Tablet Breakpoint Gap: No Column Hiding at 768–1024px
The spec called for hiding the "Expected Delivery" and "Room" columns at tablet width (768px–1024px) to keep the table readable. The @media (min-width: 768px) and (max-width: 1024px) block in the CSS only adjusts container padding and filter gap — it does not hide any table columns. This means the table at tablet shows all 8 columns (Name, Category, Status, Room, Vendor, Cost, Expected Delivery, Actions) which will be cramped on a 768–1024px viewport.
Add to the tablet media query:
@media (min-width: 768px) and (max-width: 1023px) {
.table th:nth-child(4), /* Room */
.table td:nth-child(4),
.table th:nth-child(7), /* Expected Delivery */
.table td:nth-child(7) {
display: none;
}
}Note: also change the upper bound from 1024px to 1023px to prevent overlap with the desktop breakpoint at exactly 1024px.
Medium — Sortable Table Headers Missing Keyboard Accessibility
The sortable <th> elements use onClick handlers but are plain <th> elements with no tabindex, role, or keyboard support. A user navigating by keyboard cannot activate the sort. Each sortable header should be either:
- Converted to a
<button>inside the<th>cell, or - Given
role="button"+tabindex="0"+onKeyDownhandler (Enter/Space to activate)
Additionally, sortable headers should have aria-sort="ascending" or aria-sort="descending" applied to the currently sorted column's <th>, and aria-sort="none" (or omitted) on the others. This is the correct ARIA pattern for sortable tables (ARIA 1.2 columnheader role).
Low — secondaryButton:hover Uses --color-border as Background
.secondaryButton:hover {
background-color: var(--color-border);
}--color-border is a border color token, not a background color token. The correct hover background for a secondary button is var(--color-bg-hover) (which maps to gray-50 / slate-600 in dark mode). This is the same pattern as shared.module.css:btnSecondary:hover which correctly uses var(--color-bg-hover). Note this same error exists in WorkItemsPage.module.css — this PR perpetuates rather than fixes it, and the token deviation is flagged here for consistency awareness.
Low — Action Menu aria-label Is Too Generic
Both the desktop table and mobile card versions use:
aria-label="Actions menu"This does not identify which item the menu applies to. Screen readers will announce "Actions menu, button" without knowing the item name. The label should include the item name:
aria-label={`Actions for ${item.name}`}Similarly, the menu dropdown (<div className={styles.menuDropdown}>) should have role="menu" and each menuItem button should have role="menuitem".
Low — card:hover Has No Visual Change
.card:hover {
box-shadow: var(--shadow-md);
}.card also has box-shadow: var(--shadow-md) in its base state, so the hover state is identical to the default. This gives no visual affordance that the card is interactive/hoverable. Consider elevating to var(--shadow-lg) on hover to match the pattern in other list cards.
Low — HouseholdItemStatusBadge: Badge Padding Uses Literals
.badge {
padding: 0.25rem 0.625rem;
}This should be padding: var(--spacing-1) var(--spacing-2-5). The badge is otherwise well-structured and correctly uses --radius-full, --font-size-xs, and --font-weight-medium.
Informational
- The
HouseholdItemCreatePageandHouseholdItemDetailPageare acknowledged stubs ("Coming soon" text). No structural review of their content is needed, but their CSS modules should still use tokens. - The
loadingclass inHouseholdItemsPage.module.css(text center +padding: 3rem) duplicates theshared.module.css.loadingpattern exactly. Usecomposes: loading from '../../styles/shared.module.css'to avoid divergence. - The
emptyStateclass also duplicatesshared.module.css.emptyState, though the page-level version adds a border+shadow card treatment that the shared version doesn't have — this divergence is acceptable if intentional, but worth noting. - The keyboard shortcuts integration (arrow key selection,
?for help,nfor new,/for focus search) is a strong pattern consistent with other list pages. No design concerns there.
Token Verification: In-Transit Amber Dark Mode Contrast
Spot-checking the dark mode amber values:
- Background:
rgba(245, 158, 11, 0.2)on--color-bg-primarydark (#1a1a2e) — estimated contrast ~1.2:1 (background pair, decorative) - Text:
#fcd34d(amber-300) onrgba(245,158,11,0.2)over#1a1a2e— estimated contrast ~6.5:1 — passes WCAG AA for normal text at--font-size-xs. This is acceptable.
Summary of Changes Required
| Severity | Count | Category |
|---|---|---|
| Medium | 5 | Pervasive hardcoded tokens; in-transit hex in tokens.css; missing prefers-reduced-motion; missing focus-visible; missing tablet column hiding |
| Low | 4 | border-color-as-bg on hover; generic aria-label; no-op card hover shadow; badge padding literals |
| Informational | 3 | shared.module.css reuse opportunities; emptyState divergence note; loading class duplication |
steilerDev
left a comment
There was a problem hiding this comment.
[product-architect] Reviewed PR #398 for architecture compliance, API contract adherence, code quality, and test coverage.
Verification Summary
Architecture compliance -- PASS. The implementation follows all established patterns:
- API client (
householdItemsApi.ts) mirrors the structure ofworkItemsApi.tsexactly (same import pattern, same query param building, same response unwrapping via.then(r => r.householdItem)) - Routes are flat siblings (
household-items,household-items/new,household-items/:id), consistent withwork-itemsrouting - CSS Module follows the same token-based approach as WorkItemsPage
- Page component uses the same state management, debounce, and keyboard shortcut patterns
API contract adherence -- PASS. All 5 API client functions align with the Wiki API Contract:
listHouseholdItemsmaps all 10 query parameters documented in the contract (page,pageSize,q,category,status,room,vendorId,tagId,sortBy,sortOrder)getHouseholdItem/createHouseholdItem/updateHouseholdItemcorrectly unwrap the{ householdItem: ... }envelopedeleteHouseholdItemreturnsvoidfor 204 responses- Default
pageSize(25) andsortBy(created_at) match the contract defaults
Shared types -- PASS. All types imported from @cornerstone/shared (HouseholdItemListResponse, HouseholdItemListQuery, HouseholdItemDetail, HouseholdItemSummary, HouseholdItemCategory, HouseholdItemStatus, CreateHouseholdItemRequest, UpdateHouseholdItemRequest) are correctly used and match the contract.
Design tokens -- PASS. New --color-hi-status-* tokens follow the same naming convention as existing --color-status-* tokens. Light and dark mode values provided. The amber tokens use hardcoded hex values which is consistent with the project (no amber palette exists in the token system).
Test coverage -- PASS. 53 unit tests cover:
- API client: all 5 functions including error paths (23 tests)
- Status badge: all 4 statuses with text and CSS class verification (10 tests)
- List page: loading state, error state, empty state, filters, pagination visibility, search input (20 tests)
Informational notes (no action required for this PR):
- The
.card:hovershadow is identical to the base.cardshadow (var(--shadow-md)on both), so there is no visible hover elevation change on mobile cards. Cosmetic only. - The new HI status badge tokens are not yet documented in
wiki/Style-Guide.md-- this should be addressed when the Style Guide is updated for EPIC-04 (ux-designer responsibility).
Approved (submitted as comment due to own-PR restriction).
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner] PR Review for Story 4.3: Household Items List Page (#389)
Verdict: REQUEST CHANGES (submitted as comment due to same-author constraint)
Acceptance Criteria Review
AC #1: /household-items route with heading, New Item button, and table/card list — PASS
The page renders at /household-items with an <h1> heading "Household Items", a "New Item" button in the header, a desktop table view, and a mobile card view. Routes added in App.tsx for /household-items, /household-items/new, and /household-items/:id.
AC #2: Each item displays required fields — PARTIAL FAIL
Fields present: name, category (capitalized text), purchase status (color-coded badge), room, vendor name, expected delivery date. The "Cost" column shows totalPlannedAmount via formatCurrency().
Missing: actual cost. The AC explicitly requires both "planned cost" and "actual cost" to be displayed. The implementation only shows a single "Cost" column mapped to totalPlannedAmount. The HouseholdItemSummary type from Story 4.2 only provides totalPlannedAmount — there is no totalActualAmount or equivalent field. This is a data model limitation from the API layer.
Action required: Either (a) add an actual cost column/row that shows a dash or $0.00 when no invoices exist (if the API does not yet expose this, the field can be displayed as a placeholder with a TODO for when it becomes available, similar to the computeUsedAmount pattern from Story 5.4), or (b) display two labeled columns "Planned" and "Actual" to make it explicit which cost is being shown. At minimum, the single "Cost" column should be labeled "Planned Cost" so users are not confused about which figure they are seeing.
Minor gap: category icon/badge. The AC says "category (with icon or badge)" but the implementation shows plain capitalized text. This is a non-blocking refinement item — a simple text label is functional, but the AC does specify visual distinction.
AC #3: Filter controls — NON-BLOCKING
- Category filter: implemented as single-select
<select>. AC specifies "multi-select dropdown". - Status filter: implemented as single-select
<select>. AC specifies "multi-select dropdown". - Vendor filter: implemented as single-select dropdown. AC specifies "dropdown". PASS.
- Room filter: implemented as freeform text input with debouncing. AC specifies "dropdown with distinct values from existing items".
The category and status filters need multi-select support and the room filter should be a dropdown. However, this is the same pattern identified in Story 3.5 where multi-select was specified but single-select was implemented. Downgrading to non-blocking refinement since single-select filters are fully functional. The room filter text input is actually a reasonable UX choice given rooms are freeform text in the schema.
AC #4: Search input with debounced filtering — PASS
Search input with 300ms debounce is implemented. Updates URL query params and triggers API re-fetch. Search uses the q parameter consistent with work items.
AC #5: Column sorting — PASS (with note)
Sorting is available on: name, category, status, expected delivery date, created at — all matching the AC. The AC also specifies "planned cost" and "actual cost" sorting, which are not available in the backend API's sortBy options (HouseholdItemListQuery). Additional sort options (room, order_date, updated_at) are provided beyond what AC requires, which is additive and acceptable. Cost-based sorting requires backend support; documented as refinement.
AC #6: Pagination controls — PASS
Pagination renders when totalPages > 1 with prev/next buttons, numbered page buttons (windowed to 5), and an info line showing "Showing X to Y of Z items". Page size is 25.
AC #7: Clicking item navigates to detail page — PASS
handleRowClick navigates to /household-items/${itemId}. Table rows and mobile cards both trigger navigation on click.
AC #8: New Item button navigates to create form — PASS
Button navigates to /household-items/new. Stub page exists at HouseholdItemCreatePage.
AC #9: Empty state — PASS
Two empty states: (1) "No household items yet" with "Create First Item" button when no items exist, (2) "No household items match your filters" with "Clear All Filters" button when filters produce no results.
AC #10: Status badge colors — PASS
Design tokens in tokens.css define: not_ordered (gray), ordered (blue), in_transit (amber), delivered (green). Both light and dark mode tokens are provided. HouseholdItemStatusBadge component applies the correct CSS class per status.
AC #11: Sidebar navigation includes Household Items link — PASS
The sidebar already contains a "Household Items" NavLink (pre-existing from a prior story). It is positioned after "Timeline" and before "Documents". Note: the AC text says "after Timeline and before Budget" but Budget currently appears before Timeline in the sidebar. The link's actual position (after Timeline, before Documents) is the most logical placement.
Summary
| AC | Status | Notes |
|---|---|---|
| 1 | PASS | Route, heading, button, table/card all present |
| 2 | FAIL | Actual cost missing; "Cost" column should be labeled "Planned Cost" at minimum |
| 3 | Non-blocking | Single-select instead of multi-select; room is text input instead of dropdown |
| 4 | PASS | 300ms debounce search |
| 5 | PASS (with note) | Cost sorting requires backend support |
| 6 | PASS | Full pagination |
| 7 | PASS | Row/card click navigation |
| 8 | PASS | New Item button |
| 9 | PASS | Both empty states |
| 10 | PASS | Correct badge colors |
| 11 | PASS | Pre-existing sidebar link |
Blocking Issue
AC #2: The "Cost" column must be labeled "Planned Cost" to avoid ambiguity, since actual cost is not displayed. Showing a single unlabeled "Cost" value that actually represents planned cost is misleading. This applies to both the desktop table column header and the mobile card label.
Minimum fix required: Rename the table header from "Cost" to "Planned Cost" and the card label from "Cost:" to "Planned Cost:".
Non-Blocking Observations
- Category icon/badge (AC #2): Category is shown as plain text. The AC mentions "with icon or badge." Flag for refinement.
- Multi-select filters (AC #3): Category and status use single-select. Same pattern as Story 3.5. Flag for refinement.
- Room filter type (AC #3): Text input instead of dropdown with distinct values. Functional but diverges from AC.
- Cost sorting (AC #5): Backend
HouseholdItemListQuerydoes not include cost-based sort options. - Hardcoded hex values for in-transit tokens:
#fef3c7,#92400e,#fcd34dare used since no amber base palette tokens exist. Consistent with calendar item 7 tokens. Non-blocking. - Test authorship: Verified correct. Test commit
bf70de2chasqa-integration-tester (Haiku 4.5)co-author. Frontend code commit4364ad10hasfrontend-developer (Haiku 4.5)co-author. - CI status: Quality Gates PASS, Docker PASS, E2E Smoke PASS.
Required Changes
- Rename "Cost" to "Planned Cost" in both the desktop table header (
<th>) and mobile card label (<span className={styles.cardLabel}>).
After this single change, the PR can be approved.
|
🎉 This PR is included in version 1.12.0-beta.3 🎉 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
Implements Story 4.3: Household Items List Page with full filtering, sorting, pagination, and responsive design following the exact patterns used in WorkItemsPage.
Files Created
client/src/lib/householdItemsApi.ts— API client module with CRUD functions (list, get, create, update, delete)client/src/components/HouseholdItemStatusBadge/— Status badge component for household item purchase statusesclient/src/pages/HouseholdItemCreatePage/— Stub page for story 4.4client/src/pages/HouseholdItemDetailPage/— Stub page for story 4.5Files Modified
client/src/pages/HouseholdItemsPage/HouseholdItemsPage.tsx— Complete page implementation replacing stubclient/src/pages/HouseholdItemsPage/HouseholdItemsPage.module.css— Full CSS copied from WorkItemsPage patternclient/src/styles/tokens.css— Added household item status badge tokens for light and dark modesclient/src/App.tsx— Added three routes:/household-items/{new,/:id}Key Features
List Page (
HouseholdItemsPage.tsx)n— New item/— Focus search?— Show helpDesign Tokens
Added to
tokens.css:--color-hi-status-not-ordered: gray (light: #e5e7eb / #374151; dark: #334155 / #cbd5e1)--color-hi-status-ordered: blue (light: #dbeafe / #1e40af; dark: rgba(59,130,246,0.2) / #93c5fd)--color-hi-status-in-transit: amber (light: #fef3c7 / #92400e; dark: rgba(245,158,11,0.2) / #fcd34d)--color-hi-status-delivered: green (light: #d1fae5 / #065f46; dark: rgba(16,185,129,0.15) / #a7f3d0)API Client (
householdItemsApi.ts)listHouseholdItems(params)— GET /household-items with filtersgetHouseholdItem(id)— GET /household-items/:idcreateHouseholdItem(data)— POST /household-itemsupdateHouseholdItem(id, data)— PATCH /household-items/:iddeleteHouseholdItem(id)— DELETE /household-items/:idAll functions use URLSearchParams for query strings and return typed promises matching the API contract.
Testing Checklist
Notes
fetchVendors()from existing vendorsApi.ts for vendor dropdown🤖 Generated with Claude Code