Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
a289c24
fix(ci): add GITHUB_TOKEN to CLA check workflow (#1088)
steilerDev Mar 20, 2026
daeabf1
chore: update agent memory and add .ds/ to gitignore (#1087)
steilerDev Mar 20, 2026
de7a064
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 20, 2026
640a444
fix(ci): use github.token for CLA check (#1090)
steilerDev Mar 20, 2026
c1d174a
fix(ci): set INPUT_GITHUB_TOKEN for CLA contributor-assistant action …
steilerDev Mar 20, 2026
9801e70
fix(ci): force Node.js 24 for CLA contributor-assistant action (#1092)
steilerDev Mar 20, 2026
8af88da
fix(manage): randomize color picker default on page load (#1094)
steilerDev Mar 20, 2026
e313a60
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 20, 2026
ef2ec64
fix(migrations): correct vendor_contacts column corruption from buggy…
steilerDev Mar 20, 2026
4038286
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 20, 2026
f795082
fix: render budget line edit form inline in place of card (#1101)
steilerDev Mar 21, 2026
bdd57f4
fix(budget): show medium net remaining budget on overview card (#1104)
steilerDev Mar 21, 2026
c9a0d89
DataTable core component family with filtering, sorting, pagination (…
steilerDev Mar 21, 2026
65d82df
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
37ab58a
feat(milestones): migrate MilestonesPage to shared DataTable componen…
steilerDev Mar 21, 2026
747485b
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
ed3601b
fix(e2e): use exact locator for Open menu button in design review scr…
steilerDev Mar 21, 2026
5cee6aa
fix(ci): simplify CLA workflow token and commit message config (#1109)
steilerDev Mar 21, 2026
7b5cc76
feat(user-management): migrate UserManagementPage to shared DataTable…
steilerDev Mar 21, 2026
ae097ae
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
41f5379
feat(vendors): migrate VendorsPage to shared DataTable component (#1112)
steilerDev Mar 21, 2026
ee34362
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
c9e7be7
Migrate InvoicesPage to DataTable with server-side pagination (#1114)
steilerDev Mar 21, 2026
4b119de
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
dc4bf04
feat(household-items): migrate HouseholdItemsPage to shared DataTable…
steilerDev Mar 21, 2026
fd74ac2
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
9b55bf7
fix(household-items): add missing actions menu dropdown and delete fl…
steilerDev Mar 21, 2026
555b834
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
05ff4ac
feat(work-items): migrate WorkItemsPage to shared DataTable component…
steilerDev Mar 21, 2026
936eb40
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
e1d4437
refactor(datatable): fix recurring review findings across all migrate…
steilerDev Mar 21, 2026
0d8dfc9
fix(datatable): fix filter click propagation, clear filters, and colu…
steilerDev Mar 21, 2026
06be5c1
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
058c581
feat(datatable): enhance filters with SVG icon, enum shortcuts, range…
steilerDev Mar 21, 2026
02dbd2b
feat(datatable): add budget columns to HouseholdItems, completedAt to…
steilerDev Mar 21, 2026
5e706d7
fix(datatable): auto-apply filters, fix enum labels, always-visible h…
steilerDev Mar 21, 2026
d3a9ec5
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
fc88fdd
feat(datatable): range filters, area hierarchy, column reordering, ve…
steilerDev Mar 21, 2026
d9dfd63
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
b34aa6f
fix(datatable): remove redundant headings, add invoice action menu, f…
steilerDev Mar 21, 2026
fee63ff
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 21, 2026
f922517
fix(datatable): fix filter auto-apply regression — onChange now propa…
steilerDev Mar 22, 2026
8f87fbf
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
95a15f9
fix(datatable): bundle 6 DataTable bug fixes (#1141)
steilerDev Mar 22, 2026
b492aeb
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
bea2734
chore: replace CLA workflow with custom implementation (#1148)
steilerDev Mar 22, 2026
fc9dece
chore: add branch and beta tag cleanup policies (#1157)
steilerDev Mar 22, 2026
381ba85
feat(layout): standardize list page layout across 8 pages (#1144)
steilerDev Mar 22, 2026
3771880
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
3b48603
chore: remove design review screenshots workflow and stale E2E workfl…
steilerDev Mar 22, 2026
a45ca75
fix(i18n): change "Add" to "New" in Project Overview and Schedule dia…
steilerDev Mar 22, 2026
2b1ad74
feat(i18n): add translation support for predefined category names (#1…
steilerDev Mar 22, 2026
a116edb
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
2775f6a
fix(appshell): remove empty header and add floating menu button (#1165)
steilerDev Mar 22, 2026
f86a8b7
chore: enforce PR mergeability check before CI polling in all agents …
steilerDev Mar 22, 2026
31d1da2
fix(datatable): enable numeric and date range filtering end-to-end (#…
steilerDev Mar 22, 2026
8b56a91
fix(sidebar): use dvh units to prevent footer cutoff on iPadOS Safari…
steilerDev Mar 22, 2026
d6cdc4e
fix(sidebar): remove redundant close button from mobile sidebar (#1168)
steilerDev Mar 22, 2026
85fb673
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
fc46480
fix(date-filter): replace native inputs with DateRangePicker for auto…
steilerDev Mar 22, 2026
e08deb1
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
bae4008
feat(backup): add backup & restore with scheduling, retention, and se…
steilerDev Mar 22, 2026
f41b754
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
5c4321f
fix(e2e): update page objects and tests to match DataTable component …
steilerDev Mar 22, 2026
15d00be
fix(datatable): dynamic filter bounds, date auto-apply, enable all nu…
steilerDev Mar 22, 2026
74564b3
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 22, 2026
d1971c1
refactor(milestones): remove redundant workItemCount filter logic (#1…
steilerDev Mar 22, 2026
5d72d8b
fix(e2e): fix remaining test selector and assertion failures after Da…
steilerDev Mar 23, 2026
729763d
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 23, 2026
0933828
chore(deps): bump actions/cache from 5.0.3 to 5.0.4 (#1180)
dependabot[bot] Mar 23, 2026
f5695f9
chore(deps): bump github/codeql-action from 4.33.0 to 4.34.1 (#1181)
dependabot[bot] Mar 23, 2026
fc33fa1
chore(deps-dev): bump the dev-dependencies group with 5 updates (#1183)
dependabot[bot] Mar 23, 2026
1119108
fix(ui): visual cleanup — remove dividers, redundant headings, fix ta…
steilerDev Mar 23, 2026
df38328
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 23, 2026
0c7dfe5
chore(deps): bump docker/scout-action from 1.20.2 to 1.20.3 (#1182)
dependabot[bot] Mar 24, 2026
f4f5d40
chore(deps): bump the prod-dependencies group with 2 updates (#1184)
dependabot[bot] Mar 24, 2026
a7a9e05
refactor(ui): harmonize page layout with shared PageLayout and SubNav…
steilerDev Mar 25, 2026
61c8de7
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 25, 2026
4deced3
fix(e2e): update ScheduleSubNav aria-label locator (#1190)
steilerDev Mar 25, 2026
617128a
style: auto-fix lint and format [skip ci]
steilerdev-cornerstone-bot[bot] Mar 25, 2026
8287748
fix(e2e): increase vendor URL search param timeout for mobile viewpor…
steilerDev Mar 25, 2026
aa6d703
fix(e2e): stabilize i18n category tests for CI (#1193)
steilerDev Mar 25, 2026
aeeec8f
fix(e2e): increase timeouts for work items filter empty state on mobi…
steilerDev Mar 25, 2026
2a0e1e5
docs: update README, RELEASE_SUMMARY, and .env.example for release (#…
steilerDev Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .claude/agent-memory/backend-developer/story_1030.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
name: Story #1030 Implementation
description: Migration 0028 + shared types foundation for EPIC-18 (Areas & Trades)
type: project
---

## Story #1030 Complete: Migration + Shared Types Foundation

**Status**: Implemented and pushed to `feat/1030-migration-shared-types`
**Commit**: 647acbe — feat(epic-18): Migration 0028 + shared types foundation (Story #1030)

### What Was Done

#### 1. SQL Migration (0028_areas_trades_rework.sql)

- Created `trades` table with 15 default trades (Plumbing, HVAC, Electrical, etc.)
- Migrated `vendor.specialty` string values to new trades lookup table
- Recreated `vendors` table: removed specialty column, added trade_id FK
- Recreated `vendor_contacts` table (dropped and recreated to maintain data)
- Created `areas` table with hierarchical support (name + parent_id self-reference)
- Added `area_id` and `assigned_vendor_id` columns to `work_items`
- Replaced `room` column with `area_id` on `household_items`
- Dropped all tag-related tables (`tags`, `work_item_tags`, `household_item_tags`)
- Updated default budget categories (added Waste, removed Equipment/Landscaping/Utilities/Insurance/Contingency if unused)

#### 2. New Shared Type Files

- **shared/src/types/area.ts** — AreaSummary, AreaResponse, AreaListResponse, CreateAreaRequest, UpdateAreaRequest, AreaListQuery
- **shared/src/types/trade.ts** — TradeSummary, TradeResponse, TradeListResponse, CreateTradeRequest, UpdateTradeRequest, TradeListQuery

#### 3. Updated Shared Types

**workItem.ts**:

- Removed `import type { TagResponse } from './tag.js'`
- Added `VendorSummary` interface (id + name + trade)
- WorkItemSummary: removed `tags`, added `assignedVendor` + `area`
- WorkItemDetail: removed `tags`, added `assignedVendor` + `area`
- CreateWorkItemRequest/UpdateWorkItemRequest: removed `tagIds`, added `assignedVendorId` + `areaId`
- WorkItemListQuery: removed `tagId`, added `assignedVendorId` + `areaId`

**householdItem.ts**:

- Updated HouseholdItemVendorSummary: `specialty: string | null` → `trade: TradeSummary | null`
- HouseholdItemSummary: removed `room` + `tagIds`, added `area`
- HouseholdItemDetail: removed `tags` field
- CreateHouseholdItemRequest/UpdateHouseholdItemRequest: removed `room` + `tagIds`, added `areaId`
- HouseholdItemListQuery: removed `room` + `tagId`, added `areaId`, removed 'room' from sortBy

**vendor.ts**:

- Updated Vendor interface: `specialty: string | null` → `trade: TradeSummary | null`
- CreateVendorRequest/UpdateVendorRequest: `specialty` → `tradeId`
- VendorListQuery: `specialty` → `trade` in sortBy, added `tradeId` filter

**timeline.ts**:

- Added `assignedVendor: VendorSummary | null` to TimelineWorkItem
- Added `area: AreaSummary | null` to TimelineWorkItem
- Removed `tags: TagResponse[]` from TimelineWorkItem

**budget.ts**:

- Removed duplicate VendorSummary definition
- Now imports VendorSummary from workItem.ts

**errors.ts**:

- Added `AREA_IN_USE` error code (409)
- Added `TRADE_IN_USE` error code (409)

**schema.ts** (Drizzle ORM):

- Added `trades` table definition with indexes
- Added `areas` table definition with hierarchical support (self-referencing parent_id)
- Updated `vendors` table: removed specialty, added tradeId FK
- Updated `workItems` table: added areaId and assignedVendorId FKs
- Updated `householdItems` table: removed room, added areaId FK
- Removed tags, workItemTags, householdItemTags table definitions
- Imported `type AnySQLiteColumn` for the self-referencing areas.parent_id type

**index.ts**:

- Removed tag exports (Tag, TagResponse, CreateTagRequest, UpdateTagRequest, TagListResponse)
- Added area exports (AreaSummary, AreaResponse, AreaListResponse, AreaSingleResponse, CreateAreaRequest, UpdateAreaRequest, AreaListQuery)
- Added trade exports (TradeSummary, TradeResponse, TradeListResponse, TradeSingleResponse, CreateTradeRequest, UpdateTradeRequest, TradeListQuery)
- Added VendorSummary to WorkItems exports
- Removed VendorSummary from WorkItemBudgets exports (no duplicate)

### Type Validation

All production type definitions compile cleanly (verified individually):

- area.ts ✓
- trade.ts ✓
- workItem.ts ✓
- householdItem.ts ✓
- vendor.ts ✓
- timeline.ts ✓
- budget.ts ✓
- errors.ts ✓
- workItemBudget.ts ✓

Test files in shared have expected failures (using old properties: room, tags, tagIds, specialty) — these will be fixed by QA when updating tests.

### Downstream Dependencies

Subsequent stories depend on these foundation types:

- **Story #1031 + #1032** (Areas + Trades Backend CRUD): Will implement GET/POST/PATCH/DELETE endpoints
- **Story #1033 + #1034** (Work Item + HI Rework): Will update service/route handlers to use new fields
- **Story #1035** (Frontend Manage Page): Will create UI for areas/trades management
- **Story #1037** (Frontend Entity Integration): Will add area/vendor selection to work item/HI forms

### Important Notes

- The migration uses the vendor table recreation pattern from migration 0026 (drop old, create new, copy data, rename)
- Areas support hierarchical structure via parent_id self-reference using AnySQLiteColumn type annotation
- VendorSummary is now unified (defined once in workItem.ts, imported by budget.ts) to avoid duplication
- Tags are completely removed from the database schema and API types
- No routes or services are implemented in this story — pure foundation work
55 changes: 55 additions & 0 deletions .claude/agent-memory/e2e-test-engineer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,42 @@
> Detailed notes live in topic files. This index links to them.
> See: `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-epic08-e2e.md`, `story-933-dav-vendor-contacts.md`

## i18n German Locale: page.reload() Required After setLanguage() + page.goto() (2026-03-23)

After `setLanguage(page, 'de')` + `page.goto(targetUrl)`, always add `page.reload()` before
asserting German text. Pattern from "Key page headings render in German" test (passing) confirms.
Applied in i18n.spec.ts "German sidebar" test and all three i18n-categories.spec.ts German tests.
**The FIRST German locale switch in a test file needs `test.setTimeout(30000)` and a 20s expect
timeout** for the heading assertion — i18next cold-start initialization takes 10-15s on CI.
Pattern: `test.setTimeout(30000); setLanguage(de); goto(URL); reload(); expect(heading).toBeVisible({ timeout: 20000 })`.
Extra warm-up navigations (goto('/') to confirm 'Projekt') consume the 30s budget — avoid them.

**Known flaky test**: "German locale: Manage trades tab shows 'Sanitär' instead of 'Plumbing'"
(`i18n-categories.spec.ts`) fails intermittently on CI — locale doesn't initialize before the
English page renders. Was failing before PR #1186 too (run 23429182196). Not blocking for beta PRs.

## WorkItemsPage.search(): URL-based Wait Prevents Stale-DOM Race (2026-03-23)

After `fill(query)`, add `page.waitForURL(url => url.searchParams.get('q') === query)` BEFORE
awaiting the `waitForResponse`. This confirms the debounce fired and React committed search state.
Do NOT call `waitForLoaded()` after the response — it resolves on stale DOM rows from the WebKit
clear-event response and creates a race where betaTitle stays visible for 10s. The test's own
`expect().not.toBeVisible()` retry handles DOM convergence. Same pattern for `clearSearch()`.

## Dashboard Card Dismiss Reload: Use networkidle, Not waitForResponse (2026-03-23)

For "dismissed card stays hidden after page reload" test: register `waitForResponse(GET preferences)`
before reload failed — LocaleContext fires FIRST GET and resolves the promise, but usePreferences
hook's second GET (which applies hiddenCards) arrives later. Fix: use `page.waitForLoadState('networkidle')`
AFTER `heading.waitFor({ state: 'visible', timeout: 10000 })` to ensure BOTH preference fetches
complete. The heading waitFor needs 10s timeout (not 5s actionTimeout) since SPA reinit takes time.

## Vendor Count Assertions Are Fragile (2026-03-23)

`getVendorNames().length` assertions are unreliable with parallel workers sharing the same DB.
Use `not.toContain(specificName)` instead of exact count equality. Remove `namesBefore`/
`namesAfter` length comparisons in cancel/no-create tests.

## E2E Parallel Isolation (2026-02-20)

`testPrefix` fixture in `e2e/fixtures/auth.ts` — use `async (_fixtures, use, testInfo)` (NOT `{}` — ESLint `no-empty-pattern`).
Expand All @@ -21,6 +57,25 @@ See `e2e-pom-patterns.md` for full details on:
3. **Mobile CSS-hidden table** — `display:none` elements still in DOM; `textContent()` works,
clicks fail — check `tableContainer.isVisible()` before using table rows

## DataTable Migration (EPIC-18, PR #1177) POM Fixes (2026-03-22)

After DataTable migration, three POM fix patterns applied:

- **Modal `useId()` IDs broken**: `#create-modal-title`/`#delete-modal-title` don't exist.
Always use `getByRole('dialog', { name: ... })` + `getByRole('heading', { level: 2 })` inside.
- **`confirmDeleteButton` → `btnConfirmDelete`**: WorkItems + HouseholdItems use
`sharedStyles.btnConfirmDelete` from `shared.module.css`. Selector: `[class*="btnConfirmDelete"]`.
- **Mobile card name lookup**: DataTableCard has NO `cardName` class. The render() function
runs identically for table cells AND cards. Name column with `styles.vendorLink` → use
`[class*="vendorLink"]` inside `cardsContainer`. Applied in both getVendorNames() and
openDeleteModal() mobile paths in VendorsPage.
- **HouseholdItems actions menu**: buttons are `role="button"` (default), NOT `role="menuitem"`.
Use `[class*="menuItemDanger"]:visible` filtered by text "Delete".
- **Production bug #1178**: DateRangePicker phase resets after clicking start date.
DateFilter.handleChange only fires when both dates set; DateRangePicker useEffect resets
phase when startDate stays ''. Affects datatable-date-range-picker.spec.ts and
datatable-ux-fixes.spec.ts — PRODUCTION BUG, not a test issue.

## E2E Wait Patterns: waitForResponse BEFORE the action (2026-02-23)

`page.waitForResponse(pred)` must ALWAYS be registered BEFORE the action that triggers the request.
Expand Down
56 changes: 56 additions & 0 deletions .claude/agent-memory/e2e-test-engineer/e2e-pom-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,59 @@ wraps responses in `{ budgetSource: {...} }` / `{ subsidyProgram: {...} }`.

**Not fixable in test code**. Fix must be applied to `client/src/lib/budgetSourcesApi.ts`
and `client/src/lib/subsidyProgramsApi.ts`. Tracked in GitHub issue #175.

## HTML5 Drag Events via Synthetic Dispatch (2026-03-23, PR #1177)

`page.mouse.down/move` does NOT fire HTML5 drag events (dragstart/dragover/drop).
Use `page.evaluate()` with `new DragEvent(...)` for full control.

**Critical**: `effectAllowed` can only be set in **trusted** (user-initiated) drag events.
Synthetic events via `dispatchEvent()` are untrusted — the setter is a no-op. Never test
`effectAllowed` via synthetic events. Test `draggable="true"` attribute instead.

**Working pattern** for insertion line test (tests CSS class after dragover):

```typescript
const firstHandle = await firstItem.elementHandle();
const secondHandle = await secondItem.elementHandle();
await page.evaluate(
({ source, target }) => {
const dataTransfer = new DataTransfer();
source.dispatchEvent(
new DragEvent('dragstart', { bubbles: true, cancelable: true, dataTransfer }),
);
target.dispatchEvent(
new DragEvent('dragover', {
bubbles: true,
cancelable: true,
dataTransfer,
clientY: target.getBoundingClientRect().top + 1,
}),
);
},
{ source: firstHandle!, target: secondHandle! },
);
await expect(dropAboveItem.or(dropBelowItem).first()).toBeVisible(); // use retry
```

Use `expect().toBeVisible()` (NOT `.count()`) for React state changes — retry handles async.

## Multiple Preferences GET on Page Load (2026-03-23, PR #1177)

On dashboard load, TWO components independently call `GET /api/users/me/preferences`:

1. `LocaleContext` — fetches for locale preference
2. `usePreferences` hook — fetches all preferences (incl. `dashboard.hiddenCards`)

A single `page.waitForResponse()` captures only the FIRST GET. If asserting on `hiddenCards`,
you need React to process BOTH responses. Use `expect().toHaveCount(0)` with auto-retry instead
of `waitForResponse` + immediate assertion.

## waitForSearchParams After search() (2026-03-23, PR #1177)

`WorkItemsPage.search()` was updated to call `waitForSearchParams(query)` AFTER the API
response. This ensures the URL `?q=` param is updated AND React has committed new data.
On mobile, `waitForLoaded()` resolves immediately (old cards visible) before re-render.
The URL update is the reliable indicator that `setSearchParams` (React) has committed.

`waitForSearchParams()` is now public on `WorkItemsPage` for direct use in tests.
58 changes: 58 additions & 0 deletions .claude/agent-memory/frontend-developer/stylelint-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
name: Stylelint Configuration
description: Design token enforcement via stylelint - installed and configured
type: reference
---

## Stylelint Setup (Commit: 1d9986a)

Stylelint configured in `.stylelintrc.json` (project root) with design token enforcement.

### Versions

- stylelint: 16.13.0
- stylelint-config-standard: 37.0.0
- Installed as root workspace devDependencies (shared)

### Key Enforcement Rules

**1. color-no-hex** — Prevents hardcoded hex colors

- Catches: `color: #333`
- Enforce: Use `var(--color-*)` instead
- Disabled for: `tokens.css`, `docs/`

**2. function-disallowed-list** — Prevents raw color functions

- Catches: `rgb()`, `rgba()`, `hsl()`, `hsla()`
- Enforce: Use `var(--color-*)` instead
- Disabled for: `tokens.css`, `docs/`

**3. declaration-property-value-disallowed-list** — Prevents numeric font-weight/z-index

- Catches: `font-weight: 600` or `z-index: 50`
- Enforce: Use `var(--font-weight-*)` and `var(--z-index-*)` instead
- Disabled for: `tokens.css`, `docs/`

### npm Scripts

```bash
npm run stylelint # Check for violations
npm run stylelint:fix # Auto-fix (limited rules only)
npx stylelint path/to/file.css # Test specific file
```

### Expected Behavior

- **Badge.module.css**: Passes (fully tokenized)
- **DependencySentenceDisplay.module.css**: 2 violations (numeric font-weights)
- **print.css**: 5 violations (hardcoded hex colors)
- **tokens.css**: Passes (hex colors allowed in token definitions)
- **docs/**: Passes (has separate CSS conventions)

### Integration Notes

- Not yet integrated into CI (violations don't block builds)
- Ready for manual checks and future CI integration
- When migrating existing code, run `npm run stylelint` to identify violations
- Use `var()` references from `client/src/styles/tokens.css` for all values
26 changes: 20 additions & 6 deletions .claude/agent-memory/product-architect/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,24 @@

- All API endpoints under `/api/` prefix, error shape: `{ error: { code, message, details? } }`
- Offset pagination: page (1-indexed), pageSize (default 25, max 100)
- Tags/users NOT paginated (small collections)
- PATCH with tagIds replaces entire tag set (set-semantics)
- Areas/trades/users NOT paginated (small collections)
- Junction tables use composite PKs (no surrogate id) -- EXCEPT invoice_budget_lines which uses surrogate UUID (carries itemized_amount, needs individual CRUD)

## Naming Conventions

- DB: snake_case | TS vars: camelCase | TS types: PascalCase | Files: camelCase.ts (React: PascalCase.tsx) | API: kebab-case | Env: UPPER_SNAKE_CASE

## Migrations (17 total)
## Migrations (18 total)

- 0001-0009: Auth, work items, budget, milestones, deps, actual dates, document_links
- 0010: household_items + 5 supporting tables (EPIC-04)
- 0011: household_item_budget_id FK + index on invoices (EPIC-04)
- 0012: household_item_deps + delivery date columns
- 0013-0016: HI dep cleanup, status rename, delivery date redesign, HI categories
- 0017: invoice_budget_lines junction table (EPIC-15, ADR-018)
- 0018: areas + trades, vendor trade_id, WI area_id + assigned_vendor_id, HI area_id, drop tags (EPIC-18, ADR-028)

## ADRs (ADR-001 through ADR-018)
## ADRs (ADR-001 through ADR-028)

- ADR-001-009: Tech stack + error handling
- ADR-010: Auth (sessions + OIDC + scrypt)
Expand All @@ -44,6 +44,7 @@
- ADR-015: Paperless-ngx integration (proxy + polymorphic links)
- ADR-016: Household items (separate entity with parallel structure)
- ADR-018: Invoice-budget-line junction table (M:N with XOR CHECK, ON DELETE CASCADE)
- ADR-028: Areas & Trades (structured dimensions replacing tags)

## EPIC Status

Expand All @@ -53,12 +54,13 @@
- EPIC-06 Timeline/Gantt: Complete (promoted to main, v1.10.0)
- EPIC-08 Documents: Complete (promoted to main, v1.11.0)
- EPIC-04 Household Items: Complete (promoted to main, v1.12.0)
- EPIC-15 Budget-Line Invoice Linking: In progress. Story 15.1 schema (PR #612, request changes)
- EPIC-15 Budget-Line Invoice Linking: Complete (promoted to main, v1.14.0)
- EPIC-18 Areas & Trades: In progress (ADR-028, Schema, API Contract designed)

## GitHub Wiki

- Wiki is git submodule at `wiki/`. Sync: `git submodule update --init wiki && git -C wiki pull origin master`
- ADR-001 through ADR-016, Architecture, Schema, API-Contract, Home, ADR-Index, Style-Guide, Security-Audit
- ADR-001 through ADR-028, Architecture, Schema, API-Contract, Home, ADR-Index, Style-Guide, Security-Audit
- **Always push wiki before creating PR** -- submodule ref must be committed in feature branch

### Wiki Update Discipline (CRITICAL)
Expand Down Expand Up @@ -116,6 +118,7 @@ See `epic04-household-items.md` for full details.
- `epic03-refinement.md` -- 40 consolidated refinement items from EPIC-03
- `epic05-budget.md` -- EPIC-05 budget management details
- `epic04-household-items.md` -- EPIC-04 household items architecture
- `epic18-areas-trades.md` -- EPIC-18 areas & trades structured dimensions

## EPIC-04 Review Summary (see story-reviews.md for details)

Expand Down Expand Up @@ -150,3 +153,14 @@ Invoice-budget-line junction table (migration 0017). Request changes:
### Key Lesson: XOR CHECK + ON DELETE SET NULL Incompatibility (Bug #611)

SQLite enforces CHECK constraints during FK SET NULL actions. If a table has `CHECK((a IS NOT NULL AND b IS NULL) OR ...)` and `ON DELETE SET NULL` on column `a`, deleting the referenced row triggers SET NULL which violates the XOR CHECK. Fix: use ON DELETE CASCADE instead.

## PR #1150 Review (2026-03-22) -- EPIC-19 Backup & Restore

Request changes (2 critical, 3 high, 2 medium):

- CRITICAL: Wiki not updated (API-Contract.md, Architecture.md) -- zero wiki changes in PR
- CRITICAL: CLAUDE.md env var table missing BACKUP_DIR, BACKUP_CADENCE, BACKUP_RETENTION
- HIGH: stopScheduler() never called on app close -- needs onClose hook
- HIGH: Module-level mutable state (operationInProgress, cronTask) -- testing concern
- MEDIUM: process.exit(0) bypasses Fastify graceful shutdown
- MEDIUM: createError state set but never rendered in UI (bug #1164)
Loading