Files
tessera-ctl/.planning/phases/07-dkv-fleet-module/07-06-SUMMARY.md
T
schalli d3e8e6400a
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 38s
Tessera CI/CD / Tests (push) Waiting to run
docs(07-06): complete SMTP settings UI plan
2026-06-27 17:06:04 +02:00

124 lines
7.7 KiB
Markdown

---
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<ReturnType<typeof setTimeout>>: 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 `<div className="p-4">` 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 `<label>` text. `getByLabelText('Host')` failed because the label's accessible text was "Host *" (exact match required by testing-library). The `aria-hidden="true"` approach on the `*` span also failed — @testing-library includes `aria-hidden` child text in label text content computation.
- **Fix:** Removed asterisks from label text entirely. Required fields use the `required` attribute on inputs for HTML5 native validation. The plan's UI-SPEC uses `*` as a visual convention; the semantic `required` attribute is the correct mechanism.
- **Files modified:** smtp-settings-form.tsx
- **Commit:** fd2dda6 (included in GREEN task commit)
**2. [Rule 2 - Missing] i18n keys for password toggle and test feedback not in original spec**
- **Found during:** Task 1 implementation
- **Issue:** The UI-SPEC `settings.smtp.*` namespace defined 10 keys. The form additionally needs `showPassword`, `hidePassword`, `testTesting`, `testSuccess`, `testFailed` for the toggle button aria-labels and test feedback strings.
- **Fix:** Added 5 new keys to `settings.smtp` in both de.json and en.json. Values follow the established German copy from the UI-SPEC copywriting contract.
- **Files modified:** apps/web/src/messages/de.json, apps/web/src/messages/en.json
- **Commit:** fd2dda6
## Verification Results
- `pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json` exits 0: PASS
- `pnpm --filter @tessera/web test -- --run smtp-settings`: 5/5 passed
- `grep -q "credentials: 'include'"` settings-api.ts: PASS
- `grep -q "smtp/test"` settings-api.ts: PASS
- `grep -q "categoryGeneral"` settings-sidebar.tsx: PASS
- `grep -q "/settings/general/smtp"` settings-sidebar.tsx: PASS
- `grep -q "categorySmtp"` settings-sidebar.tsx: PASS
- All 4 created files present on disk: PASS
- settings-sidebar.tsx modified: PASS
## Known Stubs
None. `settings-api.ts` targets real `/settings/smtp` endpoints from Plan 03 (SettingsController). `SmtpSettingsForm` is fully wired to `fetchSmtp/saveSmtp/testSmtp`. The SMTP page renders the form directly. No placeholder values or TODO items.
## Threat Flags
None. All STRIDE threats from the plan's threat register are mitigated:
- T-07-17: `SmtpConfig` exposes only `hasPassword`; password state always `''`; password sent only when typed; `credentials: 'include'` on all API calls
- T-07-18: Backend `@Roles(Role.ADMIN, Role.SUPER_ADMIN)` on /settings/smtp endpoints (Plan 03) is authoritative; frontend cannot bypass
- T-07-19: All backend strings rendered via React text nodes; no `dangerouslySetInnerHTML` in any new component
## Self-Check: PASSED