From 5c191c8d100fc6449edc89f0465eab6625948403 Mon Sep 17 00:00:00 2001 From: Schalli Date: Sat, 27 Jun 2026 00:43:55 +0200 Subject: [PATCH] docs(07-05): complete DKV fleet module frontend plan --- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 29 ++-- .../07-dkv-fleet-module/07-05-SUMMARY.md | 131 ++++++++++++++++++ 3 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 .planning/phases/07-dkv-fleet-module/07-05-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 0eac2f7..6d86e24 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -237,8 +237,8 @@ Decimal phases appear between their surrounding integers in numeric order. **Wave 2** *(blocked on Wave 1 completion)* -- [ ] 07-04-PLAN.md -- Pipeline integration: DkvService orchestration, dynamic scheduler, DkvController REST, module registry seed, i18n keys (DKV-01/03/04/05) -- [ ] 07-05-PLAN.md -- Module frontend: dkv-api client, main page (history + export download + "Jetzt prüfen"), inbox settings form, vehicle CRUD + CSV import (DKV-01/03/04) +- [x] 07-04-PLAN.md -- Pipeline integration: DkvService orchestration, dynamic scheduler, DkvController REST, module registry seed, i18n keys (DKV-01/03/04/05) +- [x] 07-05-PLAN.md -- Module frontend: dkv-api client, main page (history + export download + "Jetzt prüfen"), inbox settings form, vehicle CRUD + CSV import (DKV-01/03/04) - [ ] 07-06-PLAN.md -- SMTP settings frontend: settings-api client, SMTP config form + connection test, settings-sidebar "Allgemein" category (DKV-05) **UI hint**: yes @@ -256,4 +256,4 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 | 4. Marketplace & Portal Navigation | 0/4 | Not started | - | | 5. Dashboard & Calendar | 5/5 | Complete | 2026-06-24 | | 6. Desktop Client & CI/CD | 2/3 | In Progress| | -| 7. DKV Fleet Module | 3/6 | In Progress | - | +| 7. DKV Fleet Module | 5/6 | In Progress| | diff --git a/.planning/STATE.md b/.planning/STATE.md index d62feba..46849ea 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,15 +3,15 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Phase 07 Plan 04 complete -last_updated: "2026-06-26T22:30:00.000Z" -last_activity: 2026-06-26 -- Phase 07 Plan 04 completed (DKV integration plane + REST + i18n) +stopped_at: Phase 07 Plan 05 complete (DKV Fleet Module Frontend) +last_updated: "2026-06-26T22:41:27.000Z" +last_activity: 2026-06-26 -- Phase 07 Plan 05 completed (DKV frontend — dkv-api client + 3 UI surfaces) progress: total_phases: 7 completed_phases: 5 total_plans: 30 - completed_plans: 25 - percent: 83 + completed_plans: 26 + percent: 93 --- # Project State @@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-06-18) ## Current Position Phase: 07 (dkv-fleet-module) — EXECUTING -Plan: 5 of 6 -Status: Executing Phase 07 (Plan 04 complete) -Last activity: 2026-06-26 -- Phase 07 Plan 04 completed (DKV integration plane + REST + i18n) +Plan: 6 of 6 +Status: Executing Phase 07 (Plan 05 complete) +Last activity: 2026-06-26 -- Phase 07 Plan 05 completed (DKV frontend surfaces A + B) -Progress: [██████████] 100% +Progress: [█████████░] 93% ## Performance Metrics @@ -62,6 +62,7 @@ Progress: [██████████] 100% | Phase 06 P03 | 3min | 3 tasks | 3 files | | Phase 07-dkv-fleet-module P03 | 5min | 4 tasks | 8 files | | Phase 07-dkv-fleet-module P04 | 7min | 3 tasks | 8 files | +| Phase 07-dkv-fleet-module P05 | 8min | 3 tasks | 11 files | ## Accumulated Context @@ -113,6 +114,10 @@ Recent decisions affecting current work: - [07-04]: Circular dep DkvService<->DkvScheduler avoided via controller coordination after PUT config - [07-04]: CronJob resolved via require() workaround (pnpm strict isolation: transitive dep) - [07-04]: rechnungsnummer from email subject regex /d{2}-d{9}-d{3}/; fallback=email-{uid} +- [07-05]: refreshKey lift: parent increments on checkNow success; InvoiceHistoryTable reruns useEffect +- [07-05]: onItemsLoaded callback: InvoiceHistoryTable notifies parent; parent passes items to ExportFileList (avoids second fetch) +- [07-05]: Password blank on load: configToForm() always sets password=''; hasPassword boolean drives UX hint only +- [07-05]: CsvImportButton replace: two-step inline confirm (not full modal); accept=".csv" client-side guard ### Pending Todos @@ -132,6 +137,6 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-06-26T22:30:00.000Z -Stopped at: Phase 07 Plan 04 complete (DKV integration plane) -Resume file: .planning/phases/07-dkv-fleet-module/07-05-PLAN.md +Last session: 2026-06-26T22:41:27.000Z +Stopped at: Phase 07 Plan 05 complete (DKV Fleet Module Frontend) +Resume file: .planning/phases/07-dkv-fleet-module/07-06-PLAN.md diff --git a/.planning/phases/07-dkv-fleet-module/07-05-SUMMARY.md b/.planning/phases/07-dkv-fleet-module/07-05-SUMMARY.md new file mode 100644 index 0000000..d781c65 --- /dev/null +++ b/.planning/phases/07-dkv-fleet-module/07-05-SUMMARY.md @@ -0,0 +1,131 @@ +--- +phase: 07-dkv-fleet-module +plan: 05 +subsystem: dkv-frontend +tags: [dkv, nextjs, react, frontend, crud, tdd, api-client, i18n] +dependency_graph: + requires: [DkvController REST surface (07-04), dkvFleet i18n namespace (07-04), CalendarSourceForm pattern (05-02)] + provides: [dkv-api.ts typed client, DkvFleetPage (Surface A), InboxConfigForm (Surface B Tab 1), VehicleTable + CsvImportButton (Surface B Tab 2)] + affects: + - apps/web/src/lib/dkv-api.ts + - apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/components/StatusBadge.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/components/ExportFileList.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/CsvImportButton.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/vehicles/page.tsx +tech_stack: + added: [] + patterns: + - "refreshKey lift: page.tsx increments refreshKey on checkNow success; InvoiceHistoryTable reruns useEffect" + - "onItemsLoaded callback: InvoiceHistoryTable notifies parent of loaded items; parent passes to ExportFileList" + - "T-07-12 password-blank-on-load: fetchConfig returns hasPassword boolean only; password field always blank; sent only when user types" + - "T-07-13 exportFileUrl: pure helper using encodeURIComponent; no client-built raw paths" + - "T-07-14 XSS: all backend strings via React text nodes; title attribute for badge tooltips (no dangerouslySetInnerHTML)" + - "T-07-15 CsvImportButton: accept=.csv, replace mode requires explicit destructive confirm before importVehiclesCsv" + - "TDD RED/GREEN: VehicleTable.test.tsx committed before VehicleTable.tsx; bug fixed (mockResolvedValueOnce vs mockResolvedValue)" +key_files: + created: + - apps/web/src/lib/dkv-api.ts + - apps/web/src/app/(portal)/modules/dkv-fleet/page.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/components/StatusBadge.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/components/InvoiceHistoryTable.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/components/ExportFileList.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/page.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/InboxConfigForm.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/CsvImportButton.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable.test.tsx + - apps/web/src/app/(portal)/modules/dkv-fleet/vehicles/page.tsx + modified: [] +decisions: + - "InvoiceHistoryTable refetch: refreshKey prop (number) incremented by parent on checkNow success — triggers useEffect re-run; simpler than ref/callback" + - "ExportFileList data: derives filenames from history items via onItemsLoaded callback from InvoiceHistoryTable; avoids second fetch" + - "Password blank on load: configToForm() always sets password=''; placeholder shows bullet dots as UX hint; T-07-12 compliant" + - "VehicleTable test mock fix: mockResolvedValueOnce for initial load (Hans Müller), mockResolvedValue for refetch (Max Mustermann)" + - "CsvImportButton replace confirm: inline two-step (click Importieren → show confirm block → click Ersetzen) not a full modal; per UI-SPEC" + - "Vehicles route split: settings/page.tsx has tab bar; Fahrzeuge tab is a to /modules/dkv-fleet/vehicles (own route); VehicleTable imported from settings/components/" +metrics: + duration: 8min + completed: "2026-06-26T22:41:27Z" + tasks: 3 + files_created: 11 + files_modified: 0 +--- + +# Phase 07 Plan 05: DKV Fleet Module Frontend — Summary + +Typed fetch client (dkv-api.ts) and all three UI surfaces delivered: Surface A (module main page with history table + export list + "Jetzt prüfen" trigger), Surface B Tab 1 (InboxConfigForm with show/hide password and Active toggle), and Surface B Tab 2 (VehicleTable CRUD with CsvImportButton merge/replace). All STRIDE threat mitigations from the plan are implemented. VehicleTable covered by 5 passing Vitest tests (TDD RED→GREEN). + +## Tasks Completed + +| Task | Name | Commit | Key Files | +|------|------|--------|-----------| +| 1 | dkv-api client + StatusBadge + Surface A main page | 2fe7bca | dkv-api.ts, page.tsx, StatusBadge, InvoiceHistoryTable, ExportFileList | +| 2 | Settings page + InboxConfigForm (Surface B Tab 1) | 45e9a35 | settings/page.tsx, InboxConfigForm.tsx | +| 3 (RED) | VehicleTable failing tests | c739d27 | VehicleTable.test.tsx | +| 3 (GREEN) | VehicleTable + CsvImportButton + vehicles/page | 4d81b8b | VehicleTable.tsx, CsvImportButton.tsx, vehicles/page.tsx | + +## Architecture Notes + +### InvoiceHistoryTable Refetch Mechanism + +The page holds a `refreshKey` state (number). After a successful `checkNow()` call, the page increments `refreshKey` by 1. InvoiceHistoryTable receives `refreshKey` as a prop and includes it in its `useEffect` dependency array — this triggers a fresh `fetchHistory()` call. + +Additionally, InvoiceHistoryTable accepts an `onItemsLoaded(items)` callback. After each fetch, it calls this with the loaded items. The page stores these items in `historyItems` state and passes them to `ExportFileList`, which derives the 10 most recent unique export filenames from them. This avoids a second network request for export files. + +### Password Blank on Load (T-07-12) + +`configToForm()` always maps the server response to `password: ''` — the password field is never pre-filled. The server only returns `hasPassword: boolean`. The password field shows `placeholder="••••••••"` as a UX hint when `hasPassword` is true (inferred from the config). The password is only included in the `saveConfig` payload when `form.password !== ''`. + +### TDD Gate Compliance + +- RED commit: `c739d27` (VehicleTable.test.tsx only, fails with import error) +- GREEN commit: `4d81b8b` (VehicleTable.tsx + CsvImportButton.tsx + fix) +- Tests: 5 passed ✓ + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] VehicleTable test: mockResolvedValue sequencing bug** +- **Found during:** Task 3 GREEN phase (first test run) +- **Issue:** The edit-row test called `mockFetchVehicles.mockResolvedValue([Hans])` then immediately `mockFetchVehicles.mockResolvedValue([Max])`. The second call overwrote the first, making the initial component mount return Max Mustermann instead of Hans Müller. `waitFor(() => getByText('Hans Müller'))` timed out. +- **Fix:** Changed the first `mockResolvedValue` to `mockResolvedValueOnce` so the initial mount uses Hans Müller; subsequent calls (refetch after update) use the Max Mustermann value. +- **Files modified:** VehicleTable.test.tsx +- **Commit:** 4d81b8b (included in GREEN task commit) + +## Verification Results + +- `pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json` exits 0: PASS +- `pnpm --filter @tessera/web test -- --run VehicleTable`: 5/5 passed +- `grep -q "credentials: 'include'"` dkv-api.ts: PASS +- `grep -q "check-now"` dkv-api.ts: PASS +- `grep -q "useTranslations('dkvFleet')"` page.tsx: PASS +- `grep -q "role=\"switch\""` InboxConfigForm.tsx: PASS +- `grep -q "vi.mock"` VehicleTable.test.tsx: PASS +- All 11 files present on disk: PASS + +## Known Stubs + +None. All API client functions are fully wired to real `/dkv/*` endpoints (Plan 04). All UI interactions call the corresponding dkv-api functions. The module is fully operable once the backend containers are running. + +## Threat Flags + +None. All T-07-12 to T-07-15 mitigations from the plan's threat register are implemented: +- T-07-12: Password blank-on-load in configToForm(); hasPassword-only interface field +- T-07-13: exportFileUrl() + encodeURIComponent; no client-built paths +- T-07-14: All backend strings via React text nodes; StatusBadge uses title attribute +- T-07-15: accept=".csv" on hidden file input; replace requires explicit two-step confirm + +## TDD Gate Compliance + +- RED gate commit: c739d27 ✓ (test(07-05): add failing VehicleTable tests) +- GREEN gate commit: 4d81b8b ✓ (feat(07-05): VehicleTable + CsvImportButton + vehicles page) +- All tests pass: 5/5 ✓ + +## Self-Check: PASSED