Files
tessera-ctl/.planning/phases/07-dkv-fleet-module/07-06-PLAN.md
T
schalli de06794e67
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): create phase 7 execution plans for DKV fleet module
6 plans covering full pipeline: PDF parsing foundation (Wave 0),
inbox providers + export/SMTP services (Wave 1), pipeline
orchestration + frontend pages + settings UI (Wave 2). Includes
D-06 MailModule DB-config migration and Nyquist validation strategy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 18:56:59 +02:00

16 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
phase plan type wave depends_on files_modified autonomous requirements user_setup must_haves
07-dkv-fleet-module 06 execute 2
07-01
07-03
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
true
DKV-05
truths artifacts key_links
User can open /settings/general/smtp and see the SMTP configuration form
User can enter SMTP host, port, encryption, username, password, and from-address and save it
User can test the SMTP connection and see inline success/error feedback
The settings sub-sidebar shows an 'Allgemein' category with an 'SMTP' link above the existing Dashboard category
The saved password is never rendered back into the form (only a hasPassword indicator is known)
path provides contains
apps/web/src/lib/settings-api.ts Typed fetch client for GET/PUT /settings/smtp + POST /settings/smtp/test credentials: 'include'
path provides min_lines
apps/web/src/components/settings/smtp-settings-form.tsx SMTP configuration form (Surface C) 40
path provides min_lines
apps/web/src/app/(portal)/settings/general/smtp/page.tsx SMTP settings route within the settings layout 10
path provides contains
apps/web/src/app/(portal)/settings/general/smtp/smtp-settings.test.tsx Vitest coverage for the SMTP settings form (Wave 0 / VALIDATION DKV-05) vi.mock
path provides contains
apps/web/src/components/settings/settings-sidebar.tsx Settings sub-sidebar extended with the Allgemein > SMTP category categoryGeneral
from to via pattern
apps/web/src/components/settings/smtp-settings-form.tsx apps/web/src/lib/settings-api.ts (fetchSmtp/saveSmtp/testSmtp) form load + save + connection test saveSmtp|testSmtp
from to via pattern
apps/web/src/app/(portal)/settings/general/smtp/page.tsx apps/web/src/components/settings/smtp-settings-form.tsx page renders the form SmtpSettingsForm
from to via pattern
apps/web/src/components/settings/settings-sidebar.tsx /settings/general/smtp Allgemein category Link /settings/general/smtp
Deliver the shared SMTP configuration UI (Surface C from 07-UI-SPEC.md): a settings API client for `/settings/smtp`, the SMTP configuration form, the `/settings/general/smtp` route inside the settings layout, and the settings sub-sidebar extension that adds an "Allgemein" category linking to the SMTP page. Consumes the SettingsController endpoints built in Plan 03.

Purpose: This is the user-facing half of DKV-05 — SMTP settings configurable in general settings, shared across modules. Each task is a vertical slice: after Task 1 a user can save and test SMTP config; after Task 2 they can navigate to it from the settings sidebar. Output: settings-api client, smtp-settings-form, smtp page (+ test), extended settings-sidebar.

Phase Goal

Als Administrator möchte ich DKV-Tankkarten-Rechnungen automatisch aus einem E-Mail-Postfach verarbeiten lassen, damit Flotten-Tankdaten ohne manuelle Eingabe als Excel-Datei exportiert und per SMTP zugestellt werden.

This plan lets the administrator configure the shared SMTP server that the DKV export delivery (and other modules) use.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/phases/07-dkv-fleet-module/07-CONTEXT.md @.planning/phases/07-dkv-fleet-module/07-UI-SPEC.md @.planning/phases/07-dkv-fleet-module/07-PATTERNS.md @apps/web/src/lib/calendar-api.ts @apps/web/src/components/settings/calendar-source-form.tsx @apps/web/src/components/settings/settings-sidebar.tsx @apps/web/src/app/(portal)/settings/dashboard/page.tsx @apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx

Artifacts this phase produces (Plan 06 portion)

New symbols (exclude from drift verification):

  • File apps/web/src/lib/settings-api.ts: type SmtpConfig (host, port, encryption, username, fromAddress, hasPassword — never a password); functions fetchSmtp, saveSmtp, testSmtp
  • Component SmtpSettingsForm (apps/web/src/components/settings/smtp-settings-form.tsx)
  • Component SmtpSettingsPage (default export, apps/web/src/app/(portal)/settings/general/smtp/page.tsx)

Modified (existing) symbols:

  • apps/web/src/components/settings/settings-sidebar.tsx — adds the "Allgemein" category (SMTP link) above the existing Dashboard category

Path notes: The API-client path uses the repo convention apps/web/src/lib/settings-api.ts (matching apps/web/src/lib/calendar-api.ts); the checker brief's lib/api/settings.ts does not exist in the repo. The form lives at apps/web/src/components/settings/smtp-settings-form.tsx (per the checker brief, consistent with the existing components/settings/ location of calendar-source-form.tsx and settings-sidebar.tsx). The @ import alias maps to apps/web/src.

Task 1: settings-api client + SmtpSettingsForm + SMTP page (DKV-05) 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/lib/calendar-api.ts — API-client convention: `API_URL` const, `credentials: 'include'`, typed exports, throw on non-ok (mirror structure) - apps/web/src/components/settings/calendar-source-form.tsx — form pattern: controlled inputs, label `mb-1 block text-sm text-foreground`, input `h-9 w-full max-w-md rounded border border-border bg-background px-3 text-sm`, select, password input, actions row `flex gap-3`, submit `data-testid` + disabled styling - apps/web/src/app/(portal)/settings/dashboard/page.tsx — settings page heading `mb-6 text-lg font-semibold text-foreground`, fetch-in-useEffect pattern - apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx — Vitest pattern: `vi.mock('next-intl', ...)`, `vi.mock('@/lib/...')`, dynamic `await import(...)`, render/screen/waitFor/fireEvent, afterEach cleanup — mirror exactly (mock `@/lib/settings-api`) - 07-UI-SPEC.md "Surface C: General Settings SMTP" — exact field list/order (Host, Port default 587, Verschlüsselung default STARTTLS, Benutzername, Passwort w/ show-hide, Absenderadresse), `max-w-md` on all inputs, page heading `text-lg font-semibold`, form actions row `flex gap-3 pt-2`, test feedback colors + auto-clear 6s - 07-UI-SPEC.md "i18n Translation Keys" → `settings.smtp.*` + `settings.categorySmtp` (shipped by Plan 04) - 07-CONTEXT.md D-05 (SMTP in general settings: host, port, username, password, encryption, sender address) smtp-settings.test.tsx (write FIRST — RED): - On mount the form loads existing config via mocked `fetchSmtp` and populates host/port/encryption/username/fromAddress fields (password stays blank even when `hasPassword` is true) - Submitting the form calls `saveSmtp` with the entered values - Clicking "Verbindung testen" calls `testSmtp` and renders the success message when it resolves `{ success: true }` and the error message when `{ success: false }` - The password input has a working show/hide toggle button (type switches password↔text) Write `smtp-settings.test.tsx` FIRST and confirm RED before implementing — mirror calendar-settings.test.tsx mocking (mock `next-intl` to echo short strings, mock `@/lib/settings-api`). Create `apps/web/src/lib/settings-api.ts` following calendar-api.ts: `const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';`, `credentials: 'include'`, throw on non-ok. Export type `SmtpConfig` (host string, port number, encryption 'none'|'starttls'|'ssl-tls', username?, fromAddress string, hasPassword boolean — NEVER a password field) and a `SaveSmtpPayload` (same minus hasPassword, plus optional `password`). Export `fetchSmtp()` GET /settings/smtp (may return null); `saveSmtp(payload)` PUT /settings/smtp; `testSmtp(payload)` POST /settings/smtp/test → `{ success: boolean }`. Create `smtp-settings-form.tsx` (`'use client'`, exported component `SmtpSettingsForm`, `useTranslations('settings')`): on mount `fetchSmtp()` to populate controlled state; render fields in UI-SPEC Surface C order — Host (text, placeholder smtp.example.com), Port (number, default 587), Verschlüsselung (select Keine/STARTTLS/SSL-TLS → none/starttls/ssl-tls, default starttls), Benutzername (text optional), Passwort (password optional with show/hide toggle button — `type="button"`, inline eye/eye-off SVG 16×16, toggles input type), Absenderadresse (email required, placeholder tessera@example.com, with help text). All inputs `max-w-md`, using the calendar-source-form label/input classes. Never pre-fill the password from server data (config returns `hasPassword` only); only send `password` when the user types one. Actions row `flex gap-3 pt-2`: "Einstellungen speichern" (primary, disabled while saving) → `saveSmtp`; "Verbindung testen" (secondary) → `testSmtp(currentPayload)` showing inline feedback below the row (loading muted text, success green `oklch(0.40 0.15 148)`, error `text-destructive`), auto-clearing after 6 seconds. All copy via `t('smtp.*')`. Both light + dark mode must work; inline SVG only. Create `apps/web/src/app/(portal)/settings/general/smtp/page.tsx` (`'use client'`, default export `SmtpSettingsPage`, `useTranslations('settings')`): heading `

{t('smtp.title')}

` (matches settings/dashboard/page.tsx) then ``. The route sits under the existing settings layout (SettingsSidebar + back link already wrap it). pnpm --filter @tessera/web test -- --run smtp-settings && pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json && grep -q "credentials: 'include'" apps/web/src/lib/settings-api.ts && grep -q "smtp/test" apps/web/src/lib/settings-api.ts - `smtp-settings.test.tsx` exists, mocks `@/lib/settings-api` + `next-intl`, and passes (was RED before the form existed) - `settings-api.ts` exports `fetchSmtp`, `saveSmtp`, `testSmtp` (all `credentials: 'include'`) and a `SmtpConfig` type with `hasPassword` but no `password` - The form renders all Surface C fields in order with the show/hide password toggle and never pre-fills the password - "Einstellungen speichern" calls `saveSmtp`; "Verbindung testen" calls `testSmtp` and shows inline success/error feedback - `page.tsx` renders `t('smtp.title')` heading and `` - `pnpm --filter @tessera/web test` (smtp-settings) and web type-check pass The administrator can view, save, and connection-test the shared SMTP configuration from /settings/general/smtp, covered by a passing Vitest test. Task 2: Extend SettingsSidebar with the Allgemein > SMTP category apps/web/src/components/settings/settings-sidebar.tsx - apps/web/src/components/settings/settings-sidebar.tsx — current sidebar: `items` array, `isActive` helper, category header `

Trust Boundaries

Boundary Description
browser form → /settings/smtp Admin-entered SMTP host/credentials cross to the backend
API response → React render Backend-supplied SMTP config (without password) is rendered in the DOM

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-07-17 Information Disclosure smtp-settings-form / settings-api.ts mitigate SmtpConfig exposes only hasPassword, never the secret (mirrors calendar T-05-09); password field never pre-filled; password sent only when newly typed; cookie auth via credentials: 'include' (V3)
T-07-18 Tampering settings-api.ts mitigate Backend @Roles(Role.ADMIN, Role.SUPER_ADMIN) on every /settings/smtp endpoint (Plan 03) and SmtpConfigDto validation are authoritative; the client cannot bypass them
T-07-19 Injection (XSS) smtp-settings-form / settings-sidebar mitigate All backend strings rendered via React text nodes (auto-escaped); no dangerouslySetInnerHTML
T-07-SC — npm/pnpm installs n/a No new frontend dependencies are added (inline SVG icons, existing Vitest/Testing-Library only)
</threat_model>
- `pnpm --filter @tessera/web exec tsc --noEmit -p tsconfig.json` exits 0 - `pnpm --filter @tessera/web test -- --run smtp-settings` passes - SMTP form never renders a password back from the server - Sidebar Allgemein > SMTP link navigates to /settings/general/smtp with correct active state - Components honor 07-UI-SPEC color/typography/spacing tokens in light + dark mode

<success_criteria>

  • DKV-05 SMTP settings UI complete: configurable + connection-testable in general settings, password never disclosed
  • Settings sub-sidebar exposes the Allgemein > SMTP category per 07-UI-SPEC Surface C </success_criteria>
Create `.planning/phases/07-dkv-fleet-module/07-06-SUMMARY.md` when done. Record how the test-feedback auto-clear is implemented and any UI-SPEC deviations.