fix: hide the stored keyword under each tag in settings

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.
This commit is contained in:
Linus Rath
2026-11-04 08:08:26 +01:00
parent 2a726c0f4b
commit 3609b5c99f
2 changed files with 23 additions and 22 deletions
@@ -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(<KeywordSettings />);
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(<KeywordSettings />);
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(<KeywordSettings />);
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', () => {
+8 -18
View File
@@ -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({
>
<GripVertical className="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-50 cursor-grab max-sm:hidden" />
{/* On a phone the name gets a line of its own, the controls wrap under it. */}
<div className="flex min-w-0 flex-1 max-sm:basis-full">
<div
className="flex min-w-0 flex-1 max-sm:basis-full"
title={showAdvanced ? KEYWORD_PREFIX + keyword.id : undefined}
>
<TagBadge tagId={keyword.id} variant="badge" className="text-xs" />
</div>
<span
ref={keywordRef}
className="hidden md:block min-w-0 max-w-52 truncate text-xs text-muted-foreground font-mono"
title={KEYWORD_PREFIX + keyword.id}
>
{shortenedKeyword}
</span>
<Select
value={getKeywordVisibility(keyword)}
onChange={(value) => onVisibilityChange(value as KeywordVisibility)}
@@ -709,7 +700,6 @@ export function KeywordSettings() {
key={keyword.id}
keyword={keyword}
keywords={emailKeywords}
nestedTags={nestedTags}
onEdit={() => {
setEditingId(keyword.id);
setIsAdding(false);