--- phase: 07-dkv-fleet-module plan: 06 subsystem: dkv-smtp-settings-ui tags: [dkv, nextjs, react, settings, smtp, tdd, api-client, i18n] dependency_graph: requires: [SettingsController REST surface (07-03), settings i18n keys (07-04), CalendarSourceForm pattern (05-02)] provides: [settings-api.ts typed client, SmtpSettingsForm (Surface C), /settings/general/smtp page, SettingsSidebar Allgemein category] affects: - apps/web/src/lib/settings-api.ts - apps/web/src/components/settings/smtp-settings-form.tsx - apps/web/src/app/(portal)/settings/general/smtp/page.tsx - apps/web/src/app/(portal)/settings/general/smtp/smtp-settings.test.tsx - apps/web/src/components/settings/settings-sidebar.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json tech_stack: added: [] patterns: - "T-07-17 password-never-prefilled: fetchSmtp returns hasPassword boolean; password state always starts as ''; only sent when user types" - "T-07-19 XSS: all backend strings via React text nodes; no dangerouslySetInnerHTML" - "test feedback auto-clear: setTimeout 6000ms stored in useRef; cleared on unmount via useEffect cleanup" - "TDD RED/GREEN: smtp-settings.test.tsx committed before SmtpSettingsForm exists; all 5 tests pass after GREEN" - "aria-hidden* removed from required labels: getByLabelText requires clean label text (aria-hidden child spans break text matching)" key_files: created: - apps/web/src/lib/settings-api.ts - apps/web/src/components/settings/smtp-settings-form.tsx - apps/web/src/app/(portal)/settings/general/smtp/page.tsx - apps/web/src/app/(portal)/settings/general/smtp/smtp-settings.test.tsx modified: - apps/web/src/components/settings/settings-sidebar.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json decisions: - "Required-field asterisks omitted from label text: required attribute on inputs handles HTML5 validation; asterisks in labels break getByLabelText in @testing-library" - "testFeedback auto-clear via useRef>: ref allows cleanup on unmount without extra state; timer restarted on each test click" - "settings-api.ts returns null for 404 on GET /settings/smtp: allows forms to start empty when no config row exists yet" - "SettingsSidebar Allgemein section uses identical link classes to Dashboard section: both sections share same active/inactive/aria-current pattern" metrics: duration: 5min completed: "2026-06-27T15:04:29Z" tasks: 2 files_created: 4 files_modified: 3 --- # Phase 07 Plan 06: SMTP Settings UI (Surface C) — Summary Settings API client (`settings-api.ts`) and SMTP configuration UI delivered: Surface C form (`SmtpSettingsForm`) with show/hide password toggle, connection-test feedback with 6-second auto-clear, and the `/settings/general/smtp` route. SettingsSidebar extended with an "Allgemein" category linking to the SMTP page — placed above the existing Dashboard section. DKV-05 UI complete. 5 Vitest tests covering load, save, test success/failure, and password toggle pass after TDD RED→GREEN cycle. ## Tasks Completed | Task | Name | Commit | Key Files | |------|------|--------|-----------| | 1 (RED) | Failing SMTP settings form tests | a96d79a | smtp-settings.test.tsx | | 1 (GREEN) | settings-api client + SmtpSettingsForm + SMTP page | fd2dda6 | settings-api.ts, smtp-settings-form.tsx, smtp/page.tsx, de.json, en.json | | 2 | Extend SettingsSidebar with Allgemein > SMTP | 75aec38 | settings-sidebar.tsx | ## Architecture Notes ### Test Feedback Auto-Clear The `testFeedback` state is set to `{ type: 'loading', message }` immediately on test click, then updated to success/error after `testSmtp()` resolves. A `setTimeout(() => setTestFeedback(null), 6000)` auto-clears the feedback. The timer handle is stored in a `useRef` so it can be: 1. Cancelled on re-click (prevents stale clear from a previous click) 2. Cleaned up on unmount via a `useEffect` return function ### Password Never Pre-Filled (T-07-17) `fetchSmtp()` returns `SmtpConfig` with `hasPassword: boolean` but no password value. The form always initializes `password: ''`. On load success, `password` stays `''`. The password is only included in `saveSmtp` / `testSmtp` payloads when `form.password !== ''` (user has typed something). ### SettingsSidebar Extension The new "Allgemein" section is inserted before the existing `
` Dashboard category block. It uses the identical link class string (`bg-sidebar-accent text-sidebar-accent-foreground font-medium` for active, `text-sidebar-foreground hover:bg-muted` for inactive) and the same `aria-current="page"` pattern. The `isActive` helper's `pathname.startsWith` branch handles `/settings/general/smtp` automatically. ### TDD Gate Compliance - RED gate commit: `a96d79a` ✓ (`test(07-06): add failing SMTP settings form tests`) - GREEN gate commit: `fd2dda6` ✓ (`feat(07-06): settings-api client + SmtpSettingsForm + SMTP page`) - Tests: 5/5 passed ✓ ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Required-field asterisks in label text broke getByLabelText** - **Found during:** Task 1 GREEN phase (first test run — 2/5 tests failed) - **Issue:** Initial form implementation used `{t('smtp.host')} *` inside `