- widgets.calendar.formFieldUrlPlaceholderEws|Graph|Caldav|Ics + formFieldUrlHintEws in de.json und en.json
- Platzhalter identisch in beiden Sprachen, Hinweistext uebersetzt
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- CHANGELOG.md (Wurzel): Unveroeffentlicht (bwo + dyv + diese Seite, 8 Punkte) und 1.0.0 – 2026-09-15 (11 Punkte aus den Handbuechern), Alltagssprache, echte Umlaute
- next.config.ts liest CHANGELOG.md zur Bauzeit nach env.TESSERA_CHANGELOG_MD; .dockerignore-Ausnahme !CHANGELOG.md und COPY-Zeile im Web-Dockerfile
- lib/changelog.ts: filterChangelogForChannel (live ohne Unveroeffentlicht, beta/dev mit "Noch nicht freigegeben (Beta)", leerer Abschnitt ausgeblendet), 10 Tests
- Server-Seite /changelog (MDEditor.Markdown + rehype-sanitize, Hinweis auf Beta, Leer-Text), 3 Tests; Versionszeile ist Link mit aria-label, 2 Tests
- de/en: sidebar.whatsNew und Namensraum changelog; Web-Suite 49/309, tsc 0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
- SmtpConfig.bugReportRecipient (string | null) und SaveSmtpPayload.bugReportRecipient (null loescht, fehlend bewahrt)
- Eingabefeld type=email hinter der Absenderadresse mit Hinweistext; Payload traegt immer trim() || null
- Zwei Komponententests: Vorbelegung aus GET, PUT-Payload mit Wert bzw. null
- i18n settings.smtp.bugReportRecipient / bugReportRecipientHelp in de und en
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
- Knopf (Kaefer-Symbol) unmittelbar vor dem Erscheinungsbild-Schalter; captureScreenshot() laeuft VOR dem Oeffnen, Test pinnt es im toPng-Mock
- computeCaptureSize (laengste Kante 1600 px, reine Funktion, getestet), dataUrlToBlob, sendBugReport als Multipart mit credentials und ohne Content-Type-Header
- error-buffer: Ringpuffer 20, window error/unhandledrejection, console.error (Original bleibt), fetch-Wrapper nur bei !ok ohne Anfrage-Rumpf/Suchteil/Kopfzeilen (T-M97-02), idempotent, SSR-sicher; installiert in app-shell
- Dialog mit Vorschau, Haekchen, Beschreibung, Meldungen je Status 409/413/429/502/allgemein, Admin-Link auf /admin/smtp
- i18n bugReport de/en, Allowlist um "passiert"; html-to-image exakt 1.11.13, Lockfile aktualisiert
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
- admin/ldap/page.tsx: SyncResult um emailConflicts/skippedNoLogin/
entryFailures erweitert; drei neue Berichtsabschnitte (Bernstein fuer
Kollisionen, neutral fuer fehlenden Anmeldenamen, destruktiv fuer
unerwartete Fehler) — kein englischer Techniktext, kein roher
Datenbank-Wortlaut mehr im Bericht
- GroupMembersModal.tsx: TenantUser.email optional, Suchvergleich gegen
leere Adresse abgesichert (WINDOWS #15) — die Mitgliedersuche stuerzt
nicht mehr ab, sobald ein Konto ohne Adresse existiert
- users/page.tsx: User.email optional, Formular faellt auf leere
Zeichenkette zurueck, Tabellenzelle zeigt einen Gedankenstrich;
UserFormData.email bleibt bei Handanlage Pflicht
- Neue Sprachschluessel unter admin.ldap.sync in de.json/en.json,
Umlaut- und Sprachschluessel-Gate bestaetigt gruen (kein neues
Allowlist-Wort noetig)
- Neuer Testfall in groups-page.test.tsx vorab gegen den unveraenderten
Bestand rot gelaufen; Abweichung von der Plan-Fixture dokumentiert
(Kurzfassung: ein einzelnes Konto mit passendem Benutzernamen loest die
Kollision wegen OR-Kurzschlussauswertung nie aus, ein zweites,
nicht-treffendes Konto ohne Adresse schon — Vollfassung im SUMMARY)
- 651/651 API- und 233/233 Web-Tests gruen, beide Typpruefungen sauber
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
- Gemeinsamer useMemo ermittelt moduleHits/groupHits getrennt; trifft der
Suchbegriff nur eine Achse, bleibt die andere vollstaendig sichtbar
(WINDOWS #14) statt leerzulaufen
- Regressionsschutz fuer WINDOWS #6c erhalten: Spaltensuche findet eine
importierte Gruppe weiterhin unter internem UND AD-Namen
- Neue Meldung adminModules.grants.noSearchResults, wenn ein Begriff weder
Modul noch Gruppe trifft — Tabelle wird dann nicht gerendert
- Vier neue Testfaelle in grants-matrix.test.tsx vorab gegen den
unveraenderten Bestand rot gelaufen (erwartete Ursachen bestaetigt)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
- testEmailConnection im API-Klienten, POST email-config/test
- EmailAlertConfigForm: Testknopf vor Speichern, Wartezustand, gruene/rote
Rueckmeldung, Formularaenderung raeumt vorherige Rueckmeldung weg
- Beschreibungsblock der Komponente korrigiert (Knopf existiert jetzt)
- Vier neue Schluessel unter tenderRadar.emailAlerts in de/en
- vi.mock-Fabriken in EmailAlertConfigForm.test.tsx und my-sources.test.tsx
um testEmailConnection erweitert, zwei neue Testfaelle
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
Beide Zahlen im Loeschdialog-Text wurden bislang in fest formulierte
Mehrzahl-Woerter eingesetzt, weshalb "1 Mitglieder und
1 Modul-Freigaben" entstand (live beobachtet an der Gruppe "Vertrieb
Innendienst"). admin.groups.deleteConfirm.body wird in de.json und
en.json auf ICU-Plural umgestellt (memberCount und grantCount je mit
eigenem plural-Argument), im selben Stil wie das bestehende
admin.ldap.…resultSummary. Die Mock-Nachricht in groups-page.test.tsx
wird auf denselben Wortlaut nachgezogen, wodurch der zuvor rote
Einzahl-Test jetzt gruen ist; die beiden bestehenden Mehrzahl-Tests
bleiben unveraendert gruen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
- favicon.svg und apple-touch-icon.png byteidentisch nach src/app/ kopiert
(dateibasierte Next.js-Metadaten fuer den Browser-Tab)
- brand.ts: einzige Stelle im Web-Quellcode fuer die drei Markenfarben
(BRAND_YELLOW, BRAND_OLIVE, BRAND_PLATE), aus favicon.svg uebernommen
- TesseraLogo-Komponente mit den Varianten mark/horizontal/stacked, als
JSX-SVG von Hand gesetzt (kein dangerouslySetInnerHTML), Kontur-Klasse
fuer die dunkle Platte per plateOutline-Eigenschaft schaltbar
- common.brand.logoAlt in de.json und en.json ergaenzt, Schluesselparitaet
per Test abgesichert
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
- settings/page.tsx zeigt nur noch Abrufintervall + plattformweite Feeds;
Postfach- und Benachrichtigungsabschnitt entfernt (ziehen auf my-sources um)
- Anzeigepruefung der Rolle aus dem Anmelde-Speicher: unbekannt -> Platzhalter,
ADMIN/SUPER_ADMIN -> Inhalt, sonst Hinweistext + Verweis auf "Meine Quellen"
(verbindliche Pruefung bleibt serverseitig, T-17-08/@UseModule, siehe
Dateikommentar)
- Zahnrad auf der Modulseite fuehrt jetzt nach /my-sources statt /settings;
neuer Schluessel page.mySourcesTitle, alter page.settingsTitle bleibt als
Verweistext auf der Nutzerseite (Task 1) in Gebrauch
- settings.emailSectionTitle/emailSectionBody/notificationsSectionTitle aus
beiden Sprachdateien entfernt (gegengeprueft: nirgends mehr referenziert);
neue Schluessel settings.accessDeniedText, settings.rssSectionUserNote
- RssFeedSource bekommt isPlatformWide (server-derived), createRssFeed nimmt
einen scope-Parameter (personal/platform, Vorgabe personal)
- RssFeedListForm bekommt scope-Prop: personal zeigt eigene Feeds editierbar +
plattformweite als schlichte Aufzaehlung ohne Knoepfe darunter; platform
zeigt nur plattformweite Feeds editierbar
- DigestIntervalForm aus settings/page.tsx unveraendert herausgeloest (keine
neuen Beschriftungen, gleiche settings.*-Schluessel)
- my-sources/page.tsx um "Meine Feeds" und "Benachrichtigung" erweitert,
Verweis auf die Administrationsseite nur fuer ADMIN/SUPER_ADMIN
- settings/page.tsx vorgezogen auf RssFeedListForm scope="platform" (Rule 3,
eigener Type-Check-Verify sonst rot) — volle Rollenpruesung folgt Task 2
Rule 1: createRssFeed's Antwort traegt kein isPlatformWide (nur GET mappt es
serverseitig) — RssFeedListForm setzt es nach dem Anlegen lokal aus dem
verwendeten scope, sonst wuerde ein frisch angelegter plattformweiter Feed
bis zum naechsten Neuladen aus seiner eigenen Liste verschwinden.
Alert-Postfach gehoert jetzt dem einzelnen Nutzer (userId @unique) statt
dem Mandanten (D-01) — ein zweiter Kollege desselben Mandanten kann sein
eigenes Postfach anbinden. tenantId bleibt denormalisiert (SMTP-Aufloesung,
Herkunftsmarkierung), wird auf create UND update mitgeschrieben.
- Handgeschriebene Migration (prisma migrate dev verweigert die
nicht-interaktive Shell): befuellt Bestandszeilen mit dem aeltesten
aktiven Administrator ihres Mandanten, entfernt verwaiste Zeilen ohne
Administrator, ersetzt die tenantId-Eindeutigkeit durch userId.
Lokal getestet (0 Bestandszeilen lokal und auf alpha — Zaehlung im
Task-1-Checkpoint), Index-Ergebnis verifiziert.
- TenderEmailConfigService.getConfigForApi/saveConfig auf userId als
Schluessel umgestellt; saveConfig nimmt {userId, tenantId}.
- TendersController: email-config-Routen von @Roles(ADMIN,SUPER_ADMIN)
auf @UseModule('tender-radar') umgestellt (Postfach ist jetzt
Nutzereinstellung); Route-Reihenfolge vor @Get(':id') unveraendert.
- Neue Seite /modules/tender-radar/my-sources ("Meine Quellen") mit dem
unveraenderten EmailAlertConfigForm; Hinweistext benennt D-05 (Tender
bleibt plattform-global — nur wer Quellen einspeist aendert sich).
- tenders.controller.spec.ts an neue Service-Signatur angepasst (Rule 3,
nicht im Plan gelistet, aber zum Kompilieren/Bestehen erforderlich).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- SyncResult interface grows additively: groupMembershipsAdded/Removed
(D-21 backend gap, existed since Phase 15 but never wired into the
frontend) plus groupsAdopted/Renamed/Deleted and defaultMarkerMoved
(Plan 16-03).
- New syncRequestError state: a failed sync request (network error or
!res.ok) now renders a visible text-sm text-destructive line instead
of silently reporting a three-zero result as a successful no-op.
- Sync report container gains three new lines: group-membership counts,
AD-group counts (always visible, even at zero), and a conditional
amber "default marker reassigned" line shown only when
defaultMarkerMoved > 0.
- Four new i18n keys under admin.ldap.sync in de.json/en.json.
Delete the admin.groups.ldapBind.* subtree (hint/bound/unbind/
searchPlaceholder/discoverError/noResults) from de.json and en.json —
fully orphaned since GroupFormModal.tsx no longer has an AD-binding
codepath. admin.groups.ldapBinding (column header) and every other
admin.groups.* key are untouched; key sets stay in parity across both
languages.
Test file's translation stub loses the same dead ldapBind block and
gains the seven Task 1 keys. Two new cases lock down D-07: opening the
create dialog issues no /ldap/groups request, and editing an imported
group renders a disabled name input plus the internal-name field
instead of any AD-search UI.
- Remove AD radio-selection block, discovery effect/state, and the
two-step create-then-PATCH-bind flow from GroupFormModal.tsx (D-07):
a local group can no longer be bound to an AD group from this dialog.
- Add locked name field with provenance hint + AD-DN read-only line and
an editable internalName field for imported groups (D-03/D-04); create
state gets a hint linking to the LDAP import area.
- Visible save-error line (never a silent catch{}) that distinguishes a
409 name collision from a generic failure; dialog stays open, inputs
are preserved.
- Add Group.internalName to the page.tsx interface now (Rule 3 — the
modal cannot typecheck without it; Task 2 adds the display-cell usage).
- Add seven new admin.groups i18n keys to de.json/en.json (orphaned
ldapBind.* keys removed in Task 3).
Task 1 checkpoint resolved: approve-both, granted 2026-08-06 by the
project owner (D-04 one-way schema extension: Group.internalName +
Group.ldapObjectGuid, both nullable, one versioned migration).
Adds the Phase 16 tracer slice through every layer:
- Prisma schema: Group.internalName, Group.ldapObjectGuid,
@@unique([tenantId, ldapObjectGuid]) (Prisma client regenerated;
the versioned migration itself is Task 3, separately blocking).
- LdapService: listGroups() now reads objectGUID via
explicitBufferAttributes and flags alreadyImported per tenant;
new importGroupsByDn() creates a Group per checked DN with
name/ldapDn/ldapObjectGuid, reject-with-report on name collision
(P2002 on name -> nameCollisions, P2002 on ldapObjectGuid ->
skipped), never aborts the batch on one DN's error; new static
escapeLdapFilterBuffer() for Plan 16-03's later existence sweep.
- DTO/controller: ImportGroupsDto, POST /ldap/groups/import
(ADMIN/SUPER_ADMIN), listGroups route now tenant-scoped.
- Frontend: new "AD-Gruppen importieren" section in /admin/ldap,
own discovery/import handlers with a visible error state
(Owner decision 2026-08-06 — no silent catch{} for these two
handlers), i18n keys in de.json/en.json.
- Tests: 8 new cases covering the full <behavior> list plus
listGroups sort order and alreadyImported.
Flagged assumption (RESEARCH.md A1/A2): objectGUID rename-stability
and the binary filter syntax are unverified against a real AD —
this plan only WRITES the GUID, Plan 16-03 reads it back live.
- de.json/en.json: admin.groups.* (incl. ldapBind, members, deleteConfirm,
grants sub-namespaces), admin.users.grants.*, adminModules.grantsLink +
grants.* + activationDialog.*, modules.accessDenied.*, marketplace
statusNoAccess/toastNoAccess, header.admin.groups -- covers this plan's
/admin/groups surface plus the Wave 4 surfaces (permission matrix,
user-detail grants, activation dialog, 403 page, marketplace badge) so
15-07/15-08 can run in parallel without touching the translation files
- admin-sidebar.tsx: sixth nav entry "Gruppen" -> /admin/groups with a
roster/list icon (Lucide list glyph, distinct from the users icon)
- Base-DN admin field is now a multi-line textarea (one DN per line),
value stays a single newline-separated string, no schema change
- baseDnHint key added (de/en) explaining the Base-DN(s) sync scope
- groupFilter.description/emptyMeansAll reworded: group filter is an
optional extra restriction; empty selection means all users under
the base DN(s) are synced (drops the old "nothing is synced"
framing)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New-config create form now defaults syncIntervalMin to 0 (matches
backend default, auto-sync off by default)
- de+en groupFilter.description + emptyMeansAll reworded: empty
selection now says "nothing is synced" instead of "imports everyone
under the base DN" (matches the backend semantic change)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PollNowButton sits next to the settings gear in the tender-radar header,
mirrors the DKV spinner/disabled UX, and bumps a refreshKey on success to
refetch ResultsList without touching any filter. Failure surfaces an
i18n error (de/en parity). pollNow() client hits POST
/modules/tender-radar/poll-now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tender-radar settings page (with the E-Mail-Alerts form) was only
reachable by manually typing the literal URL /modules/tender-radar/settings
— no on-screen link existed, and appending /settings to the dynamic
[category]/[moduleSlug] URL returns 'Modul nicht gefunden'. Add a gear-icon
Link (mirroring dkv-fleet's pattern) pointing at the literal settings route,
plus a tenderRadar.page.settingsTitle key in de/en.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduces the tenderRadar top-level namespace in de.json/en.json covering
page, results, filter (incl. Bundesland/CPV division labels), savedSearch,
detail, coverage, settings, sourceConfig, rssFeeds and emailAlerts groups.
EN translations authored with consistent Vergabe-domain terminology
(Ausschreibung->tender, Vergabestelle->contracting authority, Frist->
deadline, Auftragswert->estimated value). tenderRadar-parity.spec.ts
enforces recursively-flattened de/en key-set equality so no follow-up edit
can silently add a string to only one locale.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a per-tenant "Skip TLS certificate verification" toggle to the LDAP
admin page so admins can connect to an AD whose ldaps:// certificate is
signed by an internal/self-signed CA (Node error: "unable to verify the
first certificate"). When enabled, ldapts is given
tlsOptions.rejectUnauthorized=false; the flag is ignored for plain ldap://
(no TLS). Defaults to full verification.
New Boolean column LdapConfig.tlsRejectUnauthorized (@default(true)) +
migration; wired through DTOs, config service, all Client creations
(test/groups/user-search/import/sync) and the test-connection endpoint. UI
checkbox with an insecure-network warning (de/en). 3 new service specs;
API 218 green, web 131 green, both apps tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an AD single-user search (by cn/sAMAccountName/displayName/mail) and a
selective import to the LDAP admin page, alongside the existing group/OU
filter. Imported users are deduped against existing ones by (ldapDn, then
username): a manually-imported user carries its ldapDn, so a later
department/group sync matches and updates it in place instead of creating a
duplicate. Search results flag alreadyImported; import skips existing users
and links a missing ldapDn. Extracted shared mapEntry/upsertMappedUser
helpers so sync and manual import resolve identity identically.
Backend: GET /ldap/users/search, POST /ldap/users/import (RFC-4515 escaped
query, ADMIN-guarded). 6 new service specs (search flags, create, skip,
ldapDn-link, denylist). Full API suite 215 green, both apps tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a per-username denylist so individual accounts (service accounts like
administrator, krbtgt, guest, ldap$) can be excluded from LDAP sync,
independent of the group/OU include-filter which only scopes the search.
- schema: LdapConfig.userExcludeList String[] (+ migration)
- sync: skip excluded usernames (case-insensitive) before recording the DN,
so an already-imported user added to the list gets deactivated next sync
- DTO / config service / controller / scheduler: thread userExcludeList through
- web: exclude-list admin UI section (add/remove/save) + de/en translations
- tests: 3 specs covering empty list, case-insensitive skip, deactivation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real directories can return many groups/OUs, making the checkbox list
tedious to scroll through. Adds a client-side search input above the
list that filters by name or DN substring (case-insensitive) as you
type, so picking the right groups for the import filter is faster.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior quick task (260707-csw) pre-filled the LDAP connection form
with one customer's specific Active Directory values (balios.ctl.local,
dc=ctl,dc=local) and a matching bind-DN hint. User clarified this was
never wanted -- Tessera is a generic multi-tenant product, and baking
one customer's infrastructure into the shared admin UI is wrong,
especially since a `dcdown -v` reinstall surfaced it unexpectedly as
seemingly-baked-in defaults on a fresh system.
Reverted to blank fields with generic example placeholders
(ldap.example.com / dc=example,dc=com / cn=admin,dc=example,dc=com),
matching the form's state before that change. Removed the now-unused
bindDnHint i18n key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The admin.ldap message block referenced throughout the LDAP admin
page (t('title'), t('connectionTitle'), t('serverUrl'), field-mapping
and sync labels, etc.) didn't exist in de.json/en.json at all, so the
whole page rendered raw translation keys instead of text -- a
pre-existing gap surfaced while testing the new AD-prefill/group-filter
feature on this same page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New-config form now defaults to the CTL Active Directory connection
values (balios.ctl.local:3268, dc=ctl,dc=local, AD person filter,
down-level bind-DN hint) with the password left blank; editing an
existing config still shows its real saved values.
Adds a group/OU import filter section: discover AD groups/OUs via
GET /ldap/groups, toggle selection or add DNs manually, persist via
PATCH /ldap/config. Empty selection keeps today's "import everyone
under base DN" behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Note widget toggle button rendered raw key "widgets.note.editMode"
instead of translated label — keys were never added to either locale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Added certManager namespace to de.json with full key set (tabs, dropZone, paste, password, or, actions, emptyState, error)
- Added certManager namespace to en.json with matching key structure
- German copy matches UI-SPEC Copywriting Contract exactly
- Both files share identical key paths under certManager
- SSRF check skipped for Exchange type (internal EWS servers are common)
- testConnectionFromConfig catches SSRF/validation errors, returns {success:false,error} instead of throwing 403
- updateSource reads existing.type to determine effective type for SSRF check
- Panel shows saveError/editSaveError on failed add/update
- Edit form initialValues now includes domain field
- i18n: calendar.saveError key added (de+en)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Prisma: domain String? added to CalendarSource model (db push applied)
- DTOs: domain in CreateCalendarSourceDto, UpdateCalendarSourceDto, new TestCalendarSourceConfigDto
- Service: domain in SOURCE_SAFE_SELECT, addSource, updateSource; new testConnectionFromConfig method
- Controller: POST /calendar/sources/test-config (before :id routes to avoid collision)
- ExchangeProvider: domain in all source interfaces; passed as 3rd arg to EWS WebCredentials
- Frontend: domain in CalendarSource/CreateSourcePayload/UpdateSourcePayload; testSourceConfig API fn
- Form: domain field (Exchange-only), "Test connection" button with idle/loading/success/error states
- i18n: de+en keys for formFieldDomain, formFieldDomainHint, formTestConnection, formTesting, formTestSuccess, formTestFailed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add marketplace.accessDenied to de.json and en.json
- Add 12 calendar form field/action keys to widgets.calendar in both locales
- Replace all hardcoded English strings in calendar-source-form.tsx with t() calls
- Remove locale-detection hack on Cancel button (was comparing t() result to English string)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SMTP configuration is an admin concern, not a per-user setting. Removed
it from the settings sidebar and relocated to /admin/smtp with a link in
the admin sidebar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSV import dialog shows format line + example before mode selection
- Exchange connection test: on 401, inline hint lists common causes
(wrong credentials, domain format, username prefix, O365 not supported)
- Both de/en translations updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- testConnection now returns { success, message? } instead of boolean so
admins see the actual EWS/IMAP error in the UI rather than "Unbekannter Fehler"
- Exchange provider: resolveFolder() maps folder string to WellKnownFolderName
(Inbox, SentItems, DeletedItems, Drafts, JunkEmail + German aliases)
- InboxConfigForm: folder field now shown for both IMAP and Exchange protocols
with Exchange-specific help text listing valid well-known names
- Controller returns testConnection result directly (no more redundant wrapping)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>