From 8ffa993f287fdefa07d15c7e269adab4a9a3c53e Mon Sep 17 00:00:00 2001 From: Frank Steiler Date: Sat, 13 Jun 2026 13:33:45 +0200 Subject: [PATCH 01/48] feat(diary): add optional vendor and work start/end time with computed duration to daily log (#1673) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(diary): add optional vendor and work start/end time with computed duration to daily log Extends daily_log diary entries with three new optional fields: vendor (via SearchPicker), work start time, and work end time (both HH:mm 24h inputs). The server resolves vendorName from vendorId for display. A computed duration (hours, 2 decimal places) is calculated client-side and shown inline between the time inputs. Cross-field validation rejects end ≤ start. DiaryMetadataSummary renders the new fields for saved entries. German translations and full unit + E2E test coverage included. Fixes #1672 Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) Co-Authored-By: Claude backend-developer (Haiku 4.5) Co-Authored-By: Claude frontend-developer (Haiku 4.5) Co-Authored-By: Claude translator (Sonnet 4.6) Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) Co-Authored-By: Claude e2e-test-engineer (Sonnet 4.5) * test(diary): add computeWorkDuration to formatters mock factories DiaryMetadataSummary now re-exports computeWorkDuration from formatters.js, so any test file that mocks formatters.js and transitively imports DiaryMetadataSummary must include the export or the suite fails with a SyntaxError. Add computeWorkDuration to the mock factories in DiaryPage.test.tsx and DiaryEntryDetailPage.area.test.tsx (matching the style of each file's existing computeActualDuration stub). Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) --------- Co-authored-by: Frank Steiler Co-authored-by: Claude dev-team-lead (Sonnet 4.6) --- .../agent-memory/e2e-test-engineer/MEMORY.md | 10 + .../qa-integration-tester/MEMORY.md | 12 + .claude/agent-memory/translator/MEMORY.md | 1 + .claude/agent-memory/ux-designer/MEMORY.md | 11 + .../DiaryEntryCard/DiaryEntryCard.test.tsx | 1 + .../DiaryEntryForm/DiaryEntryForm.module.css | 17 + .../DiaryEntryForm/DiaryEntryForm.test.tsx | 103 +++++ .../diary/DiaryEntryForm/DiaryEntryForm.tsx | 103 +++++ .../DiaryMetadataSummary.test.tsx | 304 ++++++++++++++ .../DiaryMetadataSummary.tsx | 22 ++ client/src/i18n/de/diary.json | 11 +- client/src/i18n/en/diary.json | 11 +- client/src/lib/formatters.test.ts | 46 +++ client/src/lib/formatters.ts | 22 ++ .../DiaryEntryDetailPage.area.test.tsx | 1 + .../DiaryEntryDetailPage.test.tsx | 1 + .../DiaryEntryEditPage/DiaryEntryEditPage.tsx | 27 ++ client/src/pages/DiaryPage/DiaryPage.test.tsx | 1 + e2e/fixtures/apiHelpers.ts | 24 ++ e2e/pages/DiaryEntryEditPage.ts | 25 ++ .../diary/diary-daily-log-time-vendor.spec.ts | 374 ++++++++++++++++++ server/src/services/diaryService.test.ts | 213 ++++++++++ server/src/services/diaryService.ts | 51 ++- shared/src/types/diary.ts | 8 + 24 files changed, 1393 insertions(+), 6 deletions(-) create mode 100644 client/src/components/diary/DiaryMetadataSummary/DiaryMetadataSummary.test.tsx create mode 100644 e2e/tests/diary/diary-daily-log-time-vendor.spec.ts diff --git a/.claude/agent-memory/e2e-test-engineer/MEMORY.md b/.claude/agent-memory/e2e-test-engineer/MEMORY.md index e8f47e656..960b3a416 100644 --- a/.claude/agent-memory/e2e-test-engineer/MEMORY.md +++ b/.claude/agent-memory/e2e-test-engineer/MEMORY.md @@ -3,6 +3,16 @@ > 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`, `milestones-e2e.md`, `story-1248-mass-move.md`, `photo-annotator-e2e.md` +## Daily Log Time+Vendor E2E (Story #1672, 2026-06-13) — `e2e/tests/diary/diary-daily-log-time-vendor.spec.ts` + +- `createVendorViaApi`/`deleteVendorViaApi` added to `e2e/fixtures/apiHelpers.ts` (POST/DELETE `/api/vendors`, returns `{ vendor: { id } }`). +- DiaryEntryEditPage POM: 6 new locators — `dailyLogVendorSearch` (`#daily-log-vendor`), `dailyLogVendorClearButton` (`getByRole('button', { name: 'Clear selection', exact: true })`), `workStartTimeInput` (`#work-start-time`), `workEndTimeInput` (`#work-end-time`), `workDurationDisplay` (`[role="status"][aria-atomic="true"]`), `workTimeValidationError` (`#work-time-error`). +- **SearchPicker vendor interaction**: `fill()` the input → wait for `[data-search-picker-dropdown]` → click option via `portalDropdown.getByRole('option', { name })`. Portal is in `document.body`, not scoped to any modal. +- **SearchPicker with no `initialTitle`**: DiaryEntryEditPage does NOT pass `initialTitle` to the vendor SearchPicker. When an entry is loaded with a pre-saved vendorId, the picker shows the empty search input (not selectedDisplay). Vendor can only be cleared in the SAME session after selecting it via UI (not after page reload). +- **Duration display stale-read race on WebKit**: After filling time inputs, use `await expect(workDurationDisplay).not.toHaveText('0.00 h')` BEFORE `textContent()`. The duration display updates asynchronously via React `useMemo`. +- **Validation blocking save**: In Scenario 3 (end ≤ start), use `submitButton.click()` directly, NOT `editPage.save()` — `save()` waits for a PATCH response that never fires when validation blocks the submit. +- `workDurationDisplay` locator is unique on diary edit page — no other `[role="status"][aria-atomic="true"]` elements appear there. + ## Shard 3 Promotion Blocker Fix (2026-06-12) — `e2e/tests/budget/budget-source-filter.spec.ts` - `Rapid debounce coalesces requests` test was flaky: asserted `filteredRequestCount.toBe(1)` which fails when CI runner serializes clicks beyond the 50ms debounce window. Fixed in PR #1665. diff --git a/.claude/agent-memory/qa-integration-tester/MEMORY.md b/.claude/agent-memory/qa-integration-tester/MEMORY.md index 313499273..f38164039 100644 --- a/.claude/agent-memory/qa-integration-tester/MEMORY.md +++ b/.claude/agent-memory/qa-integration-tester/MEMORY.md @@ -3,6 +3,18 @@ > Detailed notes live in topic files. This index links to them. > See: `budget-categories-story-142.md`, `e2e-pom-patterns.md`, `e2e-parallel-isolation.md`, `story-358-document-linking.md`, `story-360-document-a11y.md`, `story-epic08-e2e.md`, `story-509-manage-page.md`, `story-471-dashboard.md` +## Story #1672 — diary vendor + work-time field test patterns (2026-06-13) + +**Server TS1343 on Node 22**: The local worktree tsconfig still fails with TS1343 on `import.meta.url` in `migrate.ts` even on Node 22 — all server service tests that call `runMigrations` fail locally. CI passes. Pattern confirmed: add tests and verify they compile cleanly, expect CI green. + +**DailyLogMetadata local type alias**: `DailyLogMetadata` from `@cornerstone/shared` is imported by production code but not by the test file; define a local alias `type DailyLogMetadataTest = { vendorId?: ..., vendorName?: ..., workStart?: ..., workEnd?: ... }` to cast metadata for inspection without re-importing the shared type. + +**SearchPicker label in jsdom**: DiaryEntryForm's vendor SearchPicker (`id="daily-log-vendor"`) is not a native input — `getByLabelText` won't find it. Assert the label text via `screen.getByText('Vendor')` instead. + +**DiaryMetadataSummaryProps not exported**: The component interface is private. Reconstruct it in the test file: `interface DiaryMetadataSummaryProps { entryType: DiaryEntryType; metadata: unknown; }`. + +**DiaryMetadataSummary coverage approach**: Import `DiaryEntryType` from `@cornerstone/shared` for the local props type. Use `document.querySelector('[data-testid=...]')` to assert branch routing. Cover all 5 branches (daily_log, site_visit, delivery, issue, auto-event) plus StatusPill color variants to reach 100% statements/lines. + ## React 19 iframe onError event — RESOLVED (2026-05-29) **Background**: `onError` on `