diff --git a/docs/MAIL_HANDBOOK.md b/docs/MAIL_HANDBOOK.md index 279b95f..92f1ab8 100644 --- a/docs/MAIL_HANDBOOK.md +++ b/docs/MAIL_HANDBOOK.md @@ -497,6 +497,12 @@ Before claiming a Mail composition is production-ready: is stable. - POP3 except for a future explicit legacy download/import requirement. - A full mail client with compose/reply/move/delete/read-state mutation. +- Quick Access may launch the operating environment's configured composer via + `mailto:`. That explicit handoff is not a GovOPlaN Mail delivery: it selects + no Mail profile or credential, bypasses no Mail policy, and reports no + GovOPlaN delivery result. Recent-message and Drafts links remain read-only + deep links into the authorized Mail profile and preserve their Quick Access + return context. - Recovery-ledger adoption for future provider-side move, delete, and flag mutations; no such production path exists in the current read-only mailbox. - Proof that process-local throttling coordinates multiple workers when Redis diff --git a/src/govoplan_mail/backend/manifest.py b/src/govoplan_mail/backend/manifest.py index c8986c8..1b0f6ea 100644 --- a/src/govoplan_mail/backend/manifest.py +++ b/src/govoplan_mail/backend/manifest.py @@ -554,7 +554,11 @@ manifest = ModuleManifest( body=( "Mail contributes its full mailbox to Communication. When Quick Access is enabled, its compact provider surface " "appears inside the shared Messages drawer alongside independent Postbox and future chat contributions. " - "The shared drawer does not merge channel state, credentials, custody, delivery semantics, or authorization." + "Recent entries open the exact authorized profile, folder, and message; Drafts resolves the configured or " + "provider-detected Drafts folder, and the full Mail page keeps the versioned Quick Access return context. " + "Compose deliberately launches the user's configured mail application because GovOPlaN Mail's mailbox is " + "read-only; it neither selects a GovOPlaN transport profile nor claims a GovOPlaN delivery. The shared drawer " + "does not merge channel state, credentials, custody, delivery semantics, or authorization." ), layer="configured", documentation_types=("user", "admin"), @@ -567,7 +571,12 @@ manifest = ModuleManifest( "body": ( "Mail ordnet das vollständige Postfach Kommunikation zu. Ist der Schnellzugriff aktiviert, erscheint die kompakte " "Mail-Oberfläche gemeinsam mit unabhängigen Beiträgen aus Postbox und künftig Chat unter Nachrichten. " - "Die gemeinsame Darstellung führt weder Kanalzustand noch Zugangsdaten, Verwahrung oder Berechtigungen zusammen." + "Aktuelle Einträge öffnen das genaue berechtigte Profil, den Ordner und die Nachricht; Entwürfe verwendet " + "den konfigurierten oder vom Anbieter erkannten Entwurfsordner. Die vollständige Mail-Seite erhält den " + "versionierten Rücksprungkontext. Verfassen öffnet bewusst die konfigurierte Mail-Anwendung des Benutzers, " + "da das GovOPlaN-Mail-Postfach nur lesend arbeitet; dabei wird weder ein GovOPlaN-Transportprofil gewählt " + "noch eine GovOPlaN-Zustellung behauptet. Die gemeinsame Darstellung führt weder Kanalzustand noch " + "Zugangsdaten, Verwahrung, Zustelllogik oder Berechtigungen zusammen." ), } }, diff --git a/webui/package.json b/webui/package.json index aa6f0a9..140bf0d 100644 --- a/webui/package.json +++ b/webui/package.json @@ -26,7 +26,7 @@ } }, "scripts": { - "test:mail-ui": "rm -rf .mail-test-build && mkdir -p .mail-test-build && printf '{\"type\":\"commonjs\"}\\n' > .mail-test-build/package.json && tsc -p tsconfig.mail-tests.json && node .mail-test-build/tests/mailbox-display.test.js && node .mail-test-build/tests/mailbox-folders.test.js && node .mail-test-build/tests/mail-profile-editor-model.test.js && node .mail-test-build/tests/mail-policy-validation.test.js && node scripts/test-mailbox-icon-button-structure.mjs && node scripts/test-interface-pattern-language.mjs" + "test:mail-ui": "rm -rf .mail-test-build && mkdir -p .mail-test-build && printf '{\"type\":\"commonjs\"}\\n' > .mail-test-build/package.json && tsc -p tsconfig.mail-tests.json && node .mail-test-build/tests/mailbox-display.test.js && node .mail-test-build/tests/mailbox-folders.test.js && node .mail-test-build/tests/mailbox-launch.test.js && node .mail-test-build/tests/mail-profile-editor-model.test.js && node .mail-test-build/tests/mail-policy-validation.test.js && node scripts/test-mailbox-icon-button-structure.mjs && node scripts/test-interface-pattern-language.mjs" }, "devDependencies": { "typescript": "^5.7.2" diff --git a/webui/src/features/mail/MailQuickAccess.tsx b/webui/src/features/mail/MailQuickAccess.tsx index 39a7bf2..df8f973 100644 --- a/webui/src/features/mail/MailQuickAccess.tsx +++ b/webui/src/features/mail/MailQuickAccess.tsx @@ -1,27 +1,39 @@ import { useCallback } from "react"; -import { Mail } from "lucide-react"; +import { ExternalLink, FilePenLine, Mail, Pencil } from "lucide-react"; import { Link } from "react-router"; import { DashboardWidgetList, DismissibleAlert, LoadingFrame, + quickAccessLaunchState, useDashboardWidgetData, - type ApiSettings + type QuickAccessToolRenderContext } from "@govoplan/core-webui"; import { bootstrapMailbox, listMailServerProfiles, type MailMailboxMessageSummary } from "../../api/mail"; +import { + mailboxDraftsLaunchPath, + mailboxMessageLaunchPath +} from "./mailboxLaunch"; type MailQuickAccessData = { profileName?: string; + profileId?: string; + draftsFolder?: string | null; messages: MailMailboxMessageSummary[]; available: boolean; }; -export default function MailQuickAccess({ settings }: { settings: ApiSettings }) { +type Props = Pick< + QuickAccessToolRenderContext, + "settings" | "launchContext" | "close" +>; + +export default function MailQuickAccess({ settings, launchContext, close }: Props) { const load = useCallback(async (): Promise => { const profiles = await listMailServerProfiles(settings); const profile = profiles.find((item) => item.is_active && item.imap); @@ -29,6 +41,10 @@ export default function MailQuickAccess({ settings }: { settings: ApiSettings }) const response = await bootstrapMailbox(settings, profile.id, "INBOX", 7, 0, false); return { profileName: profile.name, + profileId: profile.id, + draftsFolder: profile.imap?.folder_mappings?.drafts + || response.folders.detected_folder_mappings?.drafts + || null, messages: response.messages.messages ?? [], available: true }; @@ -46,11 +62,33 @@ export default function MailQuickAccess({ settings }: { settings: ApiSettings }) detail: message.from_header || data?.profileName, meta: formatMessageDate(message.date), leading: