From 3609b5c99ffe6af296cd111b4fda6ba7c3a79a2a Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Wed, 4 Nov 2026 08:08:26 +0100
Subject: [PATCH] fix: hide the stored keyword under each tag in settings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Tag rows no longer show "$label:…" under the name; with advanced
settings on it is the tag's tooltip. The page and the recover section
no longer talk about JMAP keywords, and the recover section is called
"Find tags on your messages that are missing here".
The keyword-settings test asserted the "$label:…" line; it now asserts
the line is gone and the tooltip shows with advanced settings on.
---
.../__tests__/keyword-settings.test.tsx | 19 +++++++++++---
components/settings/keyword-settings.tsx | 26 ++++++-------------
2 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/components/settings/__tests__/keyword-settings.test.tsx b/components/settings/__tests__/keyword-settings.test.tsx
index ccf7be677..a3dc8a6df 100644
--- a/components/settings/__tests__/keyword-settings.test.tsx
+++ b/components/settings/__tests__/keyword-settings.test.tsx
@@ -23,10 +23,21 @@ describe('KeywordSettings', () => {
});
});
- it('shows keyword JMAP id', () => {
+ // settings-28: the stored keyword is no line of its own; with the advanced
+ // settings on it is the tag's tooltip.
+ it('does not show the stored keyword under each tag', () => {
render();
- expect(screen.getByText('$label:red')).toBeInTheDocument();
- expect(screen.getByText('$label:blue')).toBeInTheDocument();
+ expect(screen.queryByText('$label:red')).toBeNull();
+ expect(screen.queryByTitle('$label:red')).toBeNull();
+ });
+
+ it('shows the stored keyword as a tooltip with the advanced settings on', () => {
+ useSettingsStore.setState({ showAdvancedSettings: true });
+ render();
+ expect(screen.queryByText('$label:red')).toBeNull();
+ expect(screen.getByTitle('$label:red')).toBeInTheDocument();
+ expect(screen.getByTitle('$label:blue')).toBeInTheDocument();
+ useSettingsStore.setState({ showAdvancedSettings: false });
});
// MO-06: the tag order was drag-only.
@@ -180,7 +191,7 @@ describe('KeywordSettings', () => {
render();
expect(screen.getByText('Work/Clients')).toBeInTheDocument();
- expect(screen.getByText('$label:work/clients')).toBeInTheDocument();
+ expect(screen.queryByText('$label:work/clients')).toBeNull();
});
it('rejects a path that would exceed the 128-byte keyword limit of Stalwart', () => {
diff --git a/components/settings/keyword-settings.tsx b/components/settings/keyword-settings.tsx
index ffd0db089..7d36ed63a 100644
--- a/components/settings/keyword-settings.tsx
+++ b/components/settings/keyword-settings.tsx
@@ -28,12 +28,10 @@ import {
getParentKeywordId,
hasChildKeywords,
isKeywordDescendant,
- keywordLevels,
type KeywordNode,
MAX_KEYWORD_ID_LENGTH,
} from "@/lib/keyword-nesting";
-import { formatKeyword, keywordRenderings } from "@/lib/keyword-format";
-import { useShortenedText } from "@/hooks/use-shortened-text";
+import { formatKeyword } from "@/lib/keyword-format";
import { TagBadge } from "@/components/email/tag-badge";
/** Lighter, base and darker shade of each hue, one row per shade. */
@@ -70,7 +68,6 @@ export function KeywordColorPicker({
function KeywordRow({
keyword,
keywords,
- nestedTags,
onEdit,
onDelete,
onVisibilityChange,
@@ -86,7 +83,6 @@ function KeywordRow({
}: {
keyword: KeywordDefinition;
keywords: KeywordDefinition[];
- nestedTags: boolean;
onEdit: () => void;
onDelete: () => void;
onVisibilityChange: (visibility: KeywordVisibility) => void;
@@ -103,10 +99,9 @@ function KeywordRow({
const t = useTranslations("settings.keywords");
const hasChildren = hasChildKeywords(keyword.id, keywords);
const tagLabel = keyword.label || keyword.id;
- // Measured with the prefix attached, since that is what occupies the column.
- const keywordCandidates = (nestedTags ? keywordRenderings(keywordLevels(keyword.id)) : [keyword.id])
- .map((rendering) => KEYWORD_PREFIX + rendering);
- const [keywordRef, shortenedKeyword] = useShortenedText(keywordCandidates);
+ // The keyword the server stores is a detail for people who asked for the
+ // advanced settings: a tooltip on the tag, not a line of its own.
+ const showAdvanced = useSettingsStore((s) => s.showAdvancedSettings);
const visibilityOptions = [
{ value: "show", label: t("visibility.show") },
{ value: "unread", label: t("visibility.unread") },
@@ -128,16 +123,12 @@ function KeywordRow({
>
{/* On a phone the name gets a line of its own, the controls wrap under it. */}
-