fix(webmail): a barra lateral mostra so o que esta por ler

Mostrava tambem o tamanho de cada pasta. Sao numeros que nao mudam e
que ninguem esta a espera: enchiam a barra sem dizer nada de util.
Agora aparece um numero apenas onde ha correio por ler, e nada nas
restantes.

Consequencia a saber: Rascunhos nunca tem mensagens por ler - nada
marca um rascunho como lido - portanto passa a nao ter numero nenhum.
Quem quiser saber quantos rascunhos tem, ve-o no cabecalho da pasta.

O tamanho continua no tooltip do link, e o cabecalho da lista continua
a dize-lo por extenso, portanto a informacao nao se perdeu - deixou
apenas de estar sempre no campo de visao.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-08-22 15:30:17 -03:00
1 parent e4491479ef
commit 64f1a2e145
4 files changed
+101 -55

No files matched your search

@@ -133,21 +133,10 @@ export default function UserWebmailLayout({ children }: { children: React.ReactN
<Icon className={`h-[17px] w-[17px] flex-none ${isActive ? "text-indigo-500" : "text-slate-400"}`} /> <Icon className={`h-[17px] w-[17px] flex-none ${isActive ? "text-indigo-500" : "text-slate-400"}`} />
<span className="min-w-0 flex-1 text-[13.5px] leading-snug">{item.label}</span> <span className="min-w-0 flex-1 text-[13.5px] leading-snug">{item.label}</span>
<span className="flex flex-none items-center gap-1.5"> <span className="flex flex-none items-center gap-1.5">
{/* The folder's size, shown quietly beside the badge: the {/* Only what is waiting to be read. The size of every folder
sidebar reported unread only, so there was nowhere in the beside its name filled the rail with numbers that never
interface that said how much mail a folder holds. */} change and that nobody is waiting on; the folder heading
{/* The folder size, always in the same quiet style, so it still says how much mail a folder holds. */}
can never be mistaken for an unread count. */}
{badge.total > 0 && (
<span
className="text-[11.5px] tabular-nums text-slate-500"
title={t("mail.messagesInFolder", { count: badge.total })}
>
{badge.total}
</span>
)}
{/* The unread count, only when there is unread mail, and
visibly distinct from the size beside it. */}
{badge.showsUnread && ( {badge.showsUnread && (
<span <span
className={`rounded-full px-1.5 py-px text-[11px] font-medium tabular-nums ${ className={`rounded-full px-1.5 py-px text-[11px] font-medium tabular-nums ${
@@ -186,9 +175,6 @@ export default function UserWebmailLayout({ children }: { children: React.ReactN
/> />
<span className="min-w-0 flex-1 truncate text-[13.5px] leading-snug">{folder.name}</span> <span className="min-w-0 flex-1 truncate text-[13.5px] leading-snug">{folder.name}</span>
<span className="flex flex-none items-center gap-1.5 pr-5"> <span className="flex flex-none items-center gap-1.5 pr-5">
{badge.total > 0 && (
<span className="text-[11.5px] tabular-nums text-slate-500">{badge.total}</span>
)}
{badge.showsUnread && ( {badge.showsUnread && (
<span className="rounded-full bg-indigo-500/20 px-1.5 py-px text-[11px] font-medium tabular-nums text-indigo-300"> <span className="rounded-full bg-indigo-500/20 px-1.5 py-px text-[11px] font-medium tabular-nums text-indigo-300">
{badge.unread} {badge.unread}
@@ -36,17 +36,18 @@ describe("the counters the sidebar shows", () => {
expect(code(USER_LAYOUT)).not.toMatch(/unread_count\s*\?\?/); expect(code(USER_LAYOUT)).not.toMatch(/unread_count\s*\?\?/);
}); });
// The total was rendered twice for Drafts, where the badge and the size are // The sidebar shows what is waiting to be read and nothing else. It used to
// the same number: "Rascunhos 1 1". // print the size of every folder beside its name - numbers that never change
// // and that nobody is waiting on - and, worse, it printed the size in the
// Two occurrences now, not one: the standard folders and the folders the // unread badge's own place whenever nothing was unread, so an inbox of read
// user created are rendered by separate templates. What must not happen is // mail showed "14" and reading another message never moved it.
// one template printing it twice, which is what the defect was. it("renders only the unread count, never the folder size", () => {
it("renders the folder size once per folder template", () => {
const layout = code(USER_LAYOUT); const layout = code(USER_LAYOUT);
expect(layout.match(/\{badge\.total\}/g) ?? []).toHaveLength(2); // The size survives in the tooltip, which is not the rail.
// And each template guards the unread badge, so the two numbers can never const rendered = layout.match(/\{badge\.total\}/g) ?? [];
// be the same value shown twice. expect(rendered).toHaveLength(0);
// Both folder templates - the standard ones and the user's own - guard
// their badge on there actually being unread mail.
expect(layout.match(/badge\.showsUnread/g) ?? []).toHaveLength(2); expect(layout.match(/badge\.showsUnread/g) ?? []).toHaveLength(2);
}); });
+34 -27
View File
@@ -175,39 +175,46 @@ describe("the badge a sidebar folder renders", () => {
{ special_use: "archive", name: "Archive", unread_count: 0, total_count: 0 }, { special_use: "archive", name: "Archive", unread_count: 0, total_count: 0 },
]; ];
// The inbox showed "14" with nothing unread. Reading every message left it // The sidebar shows what needs attention and nothing else. Printing the size
// at 14, because 14 was the total wearing the unread badge's clothes. // of every folder beside its name filled the rail with numbers that never
it("does not show a total where an unread count belongs", () => { // change and that nobody is waiting on.
const badge = folderBadge(folders, "inbox"); it("shows the unread count when there is unread mail", () => {
expect(badge.unread).toBe(0);
expect(badge.showsUnread).toBe(false);
});
it("still reports the folder size, separately from the unread count", () => {
expect(folderBadge(folders, "inbox").total).toBe(14);
});
// Drafts counted total as its badge, so total and badge were the same
// number and the sidebar printed it twice: "Rascunhos 1 1".
it("never renders the same number twice for drafts", () => {
const badge = folderBadge(folders, "drafts");
const rendered = [badge.showsUnread ? badge.unread : null, badge.total].filter(
(v) => v !== null,
);
expect(rendered).toEqual([1]);
});
it("shows the unread count when there really is unread mail", () => {
const badge = folderBadge(folders, "junk"); const badge = folderBadge(folders, "junk");
expect(badge.showsUnread).toBe(true); expect(badge.showsUnread).toBe(true);
expect(badge.unread).toBe(3); expect(badge.unread).toBe(3);
expect(badge.total).toBe(9);
}); });
it("shows nothing at all for an empty folder", () => { // An inbox whose mail has all been read shows nothing. This is also what the
const badge = folderBadge(folders, "archive"); // original defect looked like from the other side: it showed "14" - the
// total - and reading a message never moved it.
it("shows nothing when everything has been read", () => {
expect(folderBadge(folders, "inbox").showsUnread).toBe(false);
});
it("shows nothing for a folder nothing is delivered to", () => {
expect(folderBadge(folders, "sent").showsUnread).toBe(false);
});
it("shows nothing for an empty folder", () => {
expect(folderBadge(folders, "archive").showsUnread).toBe(false);
});
// Nothing marks a draft read, so its unread count is permanently zero. With
// only unread counts on show, Drafts carries no number at all.
it("shows nothing for drafts, which are never unread", () => {
expect(folderBadge(folders, "drafts").showsUnread).toBe(false);
});
// The total is still available to anything that wants it - the folder
// heading uses it - it is simply not what the rail displays.
it("still reports the folder size for callers that need it", () => {
expect(folderBadge(folders, "inbox").total).toBe(14);
});
it("copes with a folder that is not there", () => {
const badge = folderBadge(folders, "nao-existe");
expect(badge.showsUnread).toBe(false); expect(badge.showsUnread).toBe(false);
expect(badge.total).toBe(0); expect(badge.unread).toBe(0);
}); });
}); });
+52
View File
@@ -0,0 +1,52 @@
import { describe, it, expect } from "vitest";
import { folderBadge, type FolderSummary } from "./lib/mailCounts";
/**
* What the rail actually puts on screen, folder by folder.
*
* The component renders the unread count only when showsUnread is true, so
* this is the rendered result: a number, or nothing at all. Written as a table
* because the point of the change is what the rail looks like as a whole, not
* what one folder returns.
*/
function rendered(folders: FolderSummary[], route: string): string {
const badge = folderBadge(folders, route);
return badge.showsUnread ? String(badge.unread) : "";
}
describe("what each folder shows in the rail", () => {
const folders: FolderSummary[] = [
{ special_use: "inbox", name: "INBOX", unread_count: 3, total_count: 17 },
{ special_use: "sent", name: "Sent", unread_count: 0, total_count: 2 },
{ special_use: "drafts", name: "Drafts", unread_count: 0, total_count: 1 },
{ special_use: "junk", name: "Junk", unread_count: 0, total_count: 0 },
{ special_use: "", name: "Faturas", unread_count: 5, total_count: 9 },
];
it("shows only the unread count, never the size", () => {
expect(rendered(folders, "inbox")).toBe("3");
expect(rendered(folders, "inbox")).not.toContain("17");
});
it("shows nothing for folders with nothing unread", () => {
expect(rendered(folders, "sent")).toBe("");
expect(rendered(folders, "drafts")).toBe("");
expect(rendered(folders, "junk")).toBe("");
});
it("shows the unread count on a folder the user created", () => {
expect(rendered(folders, "Faturas")).toBe("5");
});
// The whole rail, as a reader sees it: a number only where mail is waiting.
it("leaves the rail carrying one number per folder with unread mail", () => {
const all = ["inbox", "sent", "drafts", "junk", "Faturas"].map((r) => rendered(folders, r));
expect(all.filter((v) => v !== "")).toEqual(["3", "5"]);
});
it("is completely bare when everything has been read", () => {
const read = folders.map((f) => ({ ...f, unread_count: 0 }));
const all = ["inbox", "sent", "drafts", "junk", "Faturas"].map((r) => rendered(read, r));
expect(all.every((v) => v === "")).toBe(true);
});
});