From 441371554bcfc2ae3722776c3df45015b70c1bf5 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Sun, 1 Nov 2026 18:48:01 +0100
Subject: [PATCH] feat: draw the rail's add app button in the page skeleton
---
app/boot-skeleton.css | 1 +
components/boot-skeleton/shell.tsx | 4 ++++
.../boot-skeleton/use-skeleton-labels.ts | 24 +++++++++++++++++++
components/layout/navigation-rail.tsx | 3 ++-
lib/boot-skeleton/hints.ts | 2 +-
5 files changed, 32 insertions(+), 2 deletions(-)
create mode 100644 components/boot-skeleton/use-skeleton-labels.ts
diff --git a/app/boot-skeleton.css b/app/boot-skeleton.css
index 87664f686..7ce12389c 100644
--- a/app/boot-skeleton.css
+++ b/app/boot-skeleton.css
@@ -44,6 +44,7 @@
@custom-variant boot-no-calendar (&:where([data-boot-rail]:not([data-boot-rail~="calendar"]) *));
@custom-variant boot-no-contacts (&:where([data-boot-rail]:not([data-boot-rail~="contacts"]) *));
@custom-variant boot-no-files (&:where([data-boot-rail]:not([data-boot-rail~="files"]) *));
+@custom-variant boot-rail-add-app (&:where([data-boot-rail~="add-app"] *));
@custom-variant boot-rail-admin (&:where([data-boot-rail~="admin"] *));
@custom-variant boot-rail-a11y (&:where([data-boot-rail~="a11y"] *));
@custom-variant boot-rail-quota (&:where([data-boot-rail~="quota"] *));
diff --git a/components/boot-skeleton/shell.tsx b/components/boot-skeleton/shell.tsx
index 806f9e57b..af71f393c 100644
--- a/components/boot-skeleton/shell.tsx
+++ b/components/boot-skeleton/shell.tsx
@@ -16,6 +16,7 @@ import {
Keyboard,
Mail,
Menu,
+ Plus,
Settings,
Shield,
Users,
@@ -66,6 +67,9 @@ export function RailSkeleton({ active, className }: { active?: RailApp; classNam
))}
+
+
+
diff --git a/components/boot-skeleton/use-skeleton-labels.ts b/components/boot-skeleton/use-skeleton-labels.ts
new file mode 100644
index 000000000..5c828640c
--- /dev/null
+++ b/components/boot-skeleton/use-skeleton-labels.ts
@@ -0,0 +1,24 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import type { RailLabels } from "./shell";
+
+/**
+ * The few strings a page skeleton shows: the status message and the phone
+ * bottom bar's labels. For the gates of client pages, which have the message
+ * catalog; the skeleton components themselves stay free of next-intl.
+ */
+export function useSkeletonLabels(): { label: string; labels: RailLabels } {
+ const t = useTranslations();
+ return {
+ label: t("common.loading"),
+ labels: {
+ mail: t("sidebar.mail"),
+ calendar: t("sidebar.calendar"),
+ contacts: t("sidebar.contacts"),
+ files: t("sidebar.files"),
+ settings: t("sidebar.settings"),
+ admin: t("sidebar.admin"),
+ },
+ };
+}
diff --git a/components/layout/navigation-rail.tsx b/components/layout/navigation-rail.tsx
index 71653b1d8..1ebaed59c 100644
--- a/components/layout/navigation-rail.tsx
+++ b/components/layout/navigation-rail.tsx
@@ -357,13 +357,14 @@ export function NavigationRail({
if (!client) return;
const items: RailHint[] = [];
for (const item of navItems) if (item.id !== 'mail' && !item.hidden) items.push(item.id as RailHint);
+ if (sidebarAppsEnabled && onManageApps) items.push('add-app');
if (isStalwartAdmin) items.push('admin');
if (showAccessibilityButton) items.push('a11y');
if (quotaShown) items.push('quota');
writeRailHints(items);
// navItems is rebuilt every render; its inputs are listed instead.
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [client, supportsCalendar, calendarEnabled, supportsContacts, contactsEnabled, supportsFiles, filesEnabled, isStalwartAdmin, showAccessibilityButton, quotaShown]);
+ }, [client, supportsCalendar, calendarEnabled, supportsContacts, contactsEnabled, supportsFiles, filesEnabled, sidebarAppsEnabled, !!onManageApps, isStalwartAdmin, showAccessibilityButton, quotaShown]);
const getIsActive = (href: string, itemId: string) => {
if (activeAppId) return false;
diff --git a/lib/boot-skeleton/hints.ts b/lib/boot-skeleton/hints.ts
index cc93fdac9..e5125777f 100644
--- a/lib/boot-skeleton/hints.ts
+++ b/lib/boot-skeleton/hints.ts
@@ -6,7 +6,7 @@ import { BOOT_HINTS_KEY } from './keys';
* admin rights, quota), which nothing else keeps across a reload. The boot
* script turns it into `data-boot-rail` (lib/boot-skeleton/boot-script.ts).
*/
-export type RailHint = 'calendar' | 'contacts' | 'files' | 'admin' | 'a11y' | 'quota';
+export type RailHint = 'calendar' | 'contacts' | 'files' | 'add-app' | 'admin' | 'a11y' | 'quota';
let lastWritten: string | null = null;