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

7.7 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
07-dkv-fleet-module 06 dkv-smtp-settings-ui
dkv
nextjs
react
settings
smtp
tdd
api-client
i18n
requires provides affects
SettingsController REST surface (07-03)
settings i18n keys (07-04)
CalendarSourceForm pattern (05-02)
settings-api.ts typed client
SmtpSettingsForm (Surface C)
/settings/general/smtp page
SettingsSidebar Allgemein category
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
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)
created modified
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
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
duration completed tasks files_created files_modified
5min 2026-06-27T15:04:29Z 2 4 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