From 92de8e99bd163253ae7cf7422c4d55eca240f011 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Fri, 25 Sep 2026 18:35:47 +0200
Subject: [PATCH] fix: show Stalwart 1.0 symlinks as links instead of empty
folders
Bulwark counted a FileNode as a folder when it had no blobId. Stalwart
1.0 adds symlinks, which have no blob either, so they showed up as
empty folders that could be opened, and folder copies recreated them
as such. Listings now ask for `nodeType` (returned since 0.16.6) and a
single helper, `fileNodeKind()`, decides what a node is in the Files
store, the copy code and the global search, falling back to the blobId
rule when `nodeType` is missing. `nodeType` is never sent in
FileNode/set.
A symlink is shown as an inert row with a link icon. It can be renamed,
moved and deleted, but not opened, previewed, downloaded, copied or
shared, and folder copies skip it.
---
components/files/file-browser.tsx | 85 ++++++++++++-------
components/files/files-app.tsx | 2 +-
lib/__tests__/filenode-kind.test.ts | 21 +++++
lib/__tests__/jmap-filenode-client.test.ts | 11 +++
lib/__tests__/jmap-filenode-writes.test.ts | 26 ++++++
lib/global-search/__tests__/providers.test.ts | 12 +++
lib/global-search/providers/files.ts | 12 +--
lib/jmap/client.ts | 12 ++-
lib/jmap/filenode-kind.ts | 20 +++++
lib/jmap/types.ts | 5 ++
locales/ar/common.json | 1 +
locales/ca/common.json | 1 +
locales/cs/common.json | 1 +
locales/da/common.json | 1 +
locales/de/common.json | 1 +
locales/en/common.json | 1 +
locales/es/common.json | 1 +
locales/fa/common.json | 1 +
locales/fr/common.json | 1 +
locales/he/common.json | 1 +
locales/hu/common.json | 1 +
locales/it/common.json | 1 +
locales/ja/common.json | 1 +
locales/ko/common.json | 1 +
locales/lv/common.json | 1 +
locales/mn/common.json | 1 +
locales/nb/common.json | 1 +
locales/nl/common.json | 1 +
locales/pl/common.json | 1 +
locales/pt/common.json | 1 +
locales/ro/common.json | 1 +
locales/ru/common.json | 1 +
locales/sk/common.json | 1 +
locales/tr/common.json | 1 +
locales/uk/common.json | 1 +
locales/zh-TW/common.json | 1 +
locales/zh/common.json | 1 +
stores/__tests__/file-store.test.ts | 50 +++++++++++
stores/file-store.ts | 44 +++++++---
39 files changed, 275 insertions(+), 52 deletions(-)
create mode 100644 lib/__tests__/filenode-kind.test.ts
create mode 100644 lib/jmap/filenode-kind.ts
diff --git a/components/files/file-browser.tsx b/components/files/file-browser.tsx
index 8a47b7d32..b9cdcb867 100644
--- a/components/files/file-browser.tsx
+++ b/components/files/file-browser.tsx
@@ -11,7 +11,7 @@ import {
AlertCircle, Star, Clock, FolderUp,
FileArchive, FileSpreadsheet, Presentation, FileCode,
Box, PenTool, Terminal as TerminalIcon, Database, Type as TypeIcon,
- Menu, Users, Share2, SquarePen,
+ Menu, Users, Share2, SquarePen, Link,
} from "@/components/icons";
import { useIsDesktop } from "@/hooks/use-media-query";
import { Button } from "@/components/ui/button";
@@ -239,17 +239,28 @@ function getFileIconByName(name: string, size: "sm" | "lg") {
return ;
}
-function getFileIcon(resource: FileResource) {
+// A symlink's icon, whatever its name suggests: it has no content to open.
+function SymlinkIcon({ className, label }: { className: string; label: string }) {
+ return (
+
+