diff --git a/PLAN_NEXT.md b/PLAN_NEXT.md index 7261b30..5e85291 100644 --- a/PLAN_NEXT.md +++ b/PLAN_NEXT.md @@ -135,6 +135,22 @@ request); anything else → 403 and the frontend forwards to `/access?email=…` (prefilled). Rate-limited (5/hour), token by mail only, delivery failures never 500 (see §0.4 hardening). +## 0.6 Polish round (bell-tower favicon, lfx-clean flows, HTML mail) + +* Favicon simplified to a stylized bell tower (spire + shaft + bell-arch + knockout); PNGs regenerated via `npm run icons`. +* `lfx validate --level 4` now green for ALL canonical flows + regression + test (`test_lfx_validate.py`). Root cause: required form fields have no + edge (values arrive via the form_values envelope). Fix: generator writes + a single-space sentinel value for required template fields; the form + parser treats whitespace-only values as "no default". L3 + Message-vs-str warnings are known lfx-validator noise (MessageTextInput + accepts Message at runtime). +* New-input authoring docs: `backend/components/kirche/README.md`. +* Magic-link + access-notification mails are branded multipart + (text + HTML): kirchenblau header, serif headline, real button link, + German copy, email-safe tables/inline styles, no external assets. + ## 1. Locked decisions (do not re-ask) * Auth: SMTP magic-link, 15-min single-use tokens (`secrets.token_urlsafe`, diff --git a/README.md b/README.md index 7061d29..9a5ed29 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ needs judgment. Every step is documented and traceable. `LANGFLOW_COMPONENTS_PATH`). `GET /api/flows/{id}/input-schema` derives a JSON Schema + RJSF uiSchema from the deployed version; the webapp renders the matching form automatically. Flows without such a node fall back to a - single textarea. + single textarea. See + `backend/components/kirche/README.md` for authoring new form fields. - **Web UI** — landing page (*Start*), access request (*Zugang*), and the logged-in webapp: *Workflows* (choose + run with dynamic form), *Aufgaben* (approvals), *Verwaltung* (admin: access requests, import, diff --git a/backend/app/auth/mail.py b/backend/app/auth/mail.py index 0b59112..8764f18 100644 --- a/backend/app/auth/mail.py +++ b/backend/app/auth/mail.py @@ -1,4 +1,10 @@ -"""Magic-link delivery. SMTP when configured, dev log fallback otherwise.""" +"""Magic-link delivery. SMTP when configured, dev log fallback otherwise. + +Mail design follows the web branding (see frontend/src/theme.ts): +kirchenblau header, serif headline, white card on paper background. +Email-safe by construction: table layout, inline styles only, system +fonts, no external images (many clients block them / track opens). +""" from __future__ import annotations @@ -10,11 +16,72 @@ from app.settings import settings logger = logging.getLogger(__name__) +# Brand palette (mirrors frontend/src/theme.ts kirchenblau). +BLUE = "#1d4a79" +BLUE_LIGHT = "#eef4fb" +INK = "#162f4c" +MUTED = "#5b6b7c" + +BLUE = "#1d4a79" +BLUE_LIGHT = "#eef4fb" +INK = "#162f4c" +MUTED = "#5b6b7c" + def magic_link(token: str) -> str: return f"{settings.frontend_url.rstrip('/')}/login?token={token}" +def _page(title: str, intro: str, button_label: str, button_url: str | None, note: str) -> str: + """Branded HTML mail body (table layout, inline styles, no images).""" + button = "" + if button_url: + button = ( + '
| ' + f'{button_label} |
|
+
+ + {note} + + |