From 1e8f64f36e1b2ab35c080d887468a4c2e7686920 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Sun, 1 Nov 2026 19:40:21 +0100
Subject: [PATCH] feat: show the mail skeleton while a mailto link opens
---
app/(main)/protocol/mailto/page.tsx | 10 ++++++-
.../__tests__/mailto-protocol-client.test.tsx | 26 +++++++++++++++++++
.../protocol/mailto-protocol-client.tsx | 14 +++++-----
3 files changed, 43 insertions(+), 7 deletions(-)
create mode 100644 components/protocol/__tests__/mailto-protocol-client.test.tsx
diff --git a/app/(main)/protocol/mailto/page.tsx b/app/(main)/protocol/mailto/page.tsx
index f2797d699..bef4cbd4c 100644
--- a/app/(main)/protocol/mailto/page.tsx
+++ b/app/(main)/protocol/mailto/page.tsx
@@ -3,6 +3,14 @@ import { MailtoProtocolClient } from "@/components/protocol/mailto-protocol-clie
export default async function MailtoProtocolPage() {
const t = await getTranslations("protocol_handlers");
+ const tSidebar = await getTranslations("sidebar");
+ const labels = {
+ mail: tSidebar("mail"),
+ calendar: tSidebar("calendar"),
+ contacts: tSidebar("contacts"),
+ files: tSidebar("files"),
+ settings: tSidebar("settings"),
+ };
- return ;
+ return ;
}
diff --git a/components/protocol/__tests__/mailto-protocol-client.test.tsx b/components/protocol/__tests__/mailto-protocol-client.test.tsx
new file mode 100644
index 000000000..ef594d63d
--- /dev/null
+++ b/components/protocol/__tests__/mailto-protocol-client.test.tsx
@@ -0,0 +1,26 @@
+import { render } from '@testing-library/react';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+
+vi.mock('@/lib/protocol-handlers/session', () => ({
+ // Still asking an open tab whether it takes the message.
+ requestOpenMailtoInExistingClient: vi.fn(() => new Promise(() => {})),
+ savePendingMailto: vi.fn(),
+}));
+
+const { MailtoProtocolClient } = await import('../mailto-protocol-client');
+
+afterEach(() => {
+ window.history.replaceState(null, '', '/');
+});
+
+describe('mailto handler page', () => {
+ // It hands over to the mail page, where the composer opens: it draws that
+ // page's skeleton instead of a line of text on a blank page.
+ it('draws the mail skeleton with the opening text as its status', () => {
+ window.history.replaceState(null, '', '/protocol/mailto?url=mailto%3Aa%40example.com');
+ const { container } = render();
+ const region = container.querySelector('[data-boot-skeleton="mail"]');
+ expect(region).not.toBeNull();
+ expect(region!.querySelector('[role="status"]')?.textContent).toBe('Opening email…');
+ });
+});
diff --git a/components/protocol/mailto-protocol-client.tsx b/components/protocol/mailto-protocol-client.tsx
index e6b8e2b1d..70bd5ed54 100644
--- a/components/protocol/mailto-protocol-client.tsx
+++ b/components/protocol/mailto-protocol-client.tsx
@@ -1,6 +1,8 @@
"use client";
import { useEffect } from "react";
+import { MailSkeleton } from "@/components/boot-skeleton/mail-skeleton";
+import type { RailLabels } from "@/components/boot-skeleton/shell";
import { parseMailto } from "@/lib/protocol-handlers/mailto";
import { requestOpenMailtoInExistingClient, savePendingMailto } from "@/lib/protocol-handlers/session";
import { useSettingsStore } from "@/stores/settings-store";
@@ -51,9 +53,11 @@ async function focusExistingClient() {
interface MailtoProtocolClientProps {
openingText: string;
+ /** The phone bottom bar's labels for the skeleton. */
+ labels?: RailLabels;
}
-export function MailtoProtocolClient({ openingText }: MailtoProtocolClientProps) {
+export function MailtoProtocolClient({ openingText, labels }: MailtoProtocolClientProps) {
useEffect(() => {
let cancelled = false;
@@ -99,9 +103,7 @@ export function MailtoProtocolClient({ openingText }: MailtoProtocolClientProps)
};
}, []);
- return (
-
- {openingText}
-
- );
+ // The mail page this hands over to, where the composer opens: its skeleton,
+ // with the opening text as the status message.
+ return ;
}