@remit/web-client 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/web-client",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
6
6
  "exports": {
@@ -145,11 +145,26 @@ describe("groupBriefSections", () => {
145
145
 
146
146
  // --- Fallback to personal ---
147
147
 
148
- test("missing category falls back to personal", () => {
148
+ test("missing category lands in its own Unclassified section", () => {
149
+ // Never folded into Personal: unclassified mail is work the classifier
150
+ // has not done, and hiding it inside Personal is what made issue #45
151
+ // look like a working classifier with a huge personal inbox.
149
152
  const r = row({ id: "1" });
150
153
  const sections = groupBriefSections([r]);
151
154
  assert.strictEqual(sections.length, 1);
152
- assert.strictEqual(sections[0].id, "personal");
155
+ assert.strictEqual(sections[0].id, "uncategorized");
156
+ });
157
+
158
+ test("uncategorized rows do not inflate the personal section", () => {
159
+ const sections = groupBriefSections([
160
+ row({ id: "1", category: "personal" }),
161
+ row({ id: "2", category: "uncategorized" }),
162
+ row({ id: "3", category: "uncategorized" }),
163
+ ]);
164
+ const personal = sections.find((s) => s.id === "personal");
165
+ const unclassified = sections.find((s) => s.id === "uncategorized");
166
+ assert.strictEqual(personal?.threads.length, 1);
167
+ assert.strictEqual(unclassified?.threads.length, 2);
153
168
  });
154
169
 
155
170
  // --- Starred is a row marker, not a section (Flagged lives in the nav) ---
package/src/lib/brief.ts CHANGED
@@ -10,9 +10,12 @@
10
10
  * 4. Marketing
11
11
  * 5. Social
12
12
  * 6. Automated
13
+ * 7. Unclassified
13
14
  *
14
15
  * Each row lands in the section for its category; a row with no category counts
15
- * as `personal` (the classifier's own fallback). Starred mail is not a section —
16
+ * as `uncategorized`, which is its own section rather than being folded into
17
+ * Personal — unclassified mail is missing work, not a decision (issue #45).
18
+ * Starred mail is not a section —
16
19
  * the star is a per-row marker, so a starred message stays in its category.
17
20
  *
18
21
  * Sender trust (vip/wellknown) no longer sections the brief — the signal is
@@ -87,6 +90,11 @@ const CATEGORY_SECTIONS: ReadonlyArray<{
87
90
  { id: "marketing", label: "Marketing", category: MessageCategory.marketing },
88
91
  { id: "social", label: "Social", category: MessageCategory.social },
89
92
  { id: "automated", label: "Automated", category: MessageCategory.automated },
93
+ {
94
+ id: "uncategorized",
95
+ label: "Unclassified",
96
+ category: MessageCategory.uncategorized,
97
+ },
90
98
  ];
91
99
 
92
100
  /**
@@ -104,9 +112,9 @@ export function groupBriefSections(rows: ThreadRowData[]): ThreadSection[] {
104
112
  );
105
113
 
106
114
  for (const row of rows) {
107
- const category = row.category ?? MessageCategory.personal;
115
+ const category = row.category ?? MessageCategory.uncategorized;
108
116
  const bucket =
109
- byCategory.get(category) ?? byCategory.get(MessageCategory.personal);
117
+ byCategory.get(category) ?? byCategory.get(MessageCategory.uncategorized);
110
118
  bucket?.push(row);
111
119
  }
112
120
 
@@ -0,0 +1,29 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, test } from "node:test";
3
+ import { toDisplayCategory } from "./display-category.js";
4
+
5
+ describe("toDisplayCategory", () => {
6
+ test("shows an unclassified message as unclassified, not personal", () => {
7
+ // Collapsing `uncategorized` into `personal` made "the classifier never
8
+ // ran" indistinguishable from "the classifier decided this is personal"
9
+ // (issue #45).
10
+ assert.strictEqual(toDisplayCategory("uncategorized"), "uncategorized");
11
+ });
12
+
13
+ test("treats a row with no category as unclassified", () => {
14
+ assert.strictEqual(toDisplayCategory(undefined), "uncategorized");
15
+ });
16
+
17
+ test("passes every classified category through unchanged", () => {
18
+ for (const category of [
19
+ "personal",
20
+ "newsletter",
21
+ "marketing",
22
+ "automated",
23
+ "transactional",
24
+ "social",
25
+ ] as const) {
26
+ assert.strictEqual(toDisplayCategory(category), category);
27
+ }
28
+ });
29
+ });
@@ -5,16 +5,18 @@ import type { ThreadCategory } from "@remit/ui";
5
5
  * Map an API message category to a kit display category.
6
6
  *
7
7
  * The API category is total (RFC 032 Tier 2) and carries `uncategorized` for
8
- * messages that are metadata-synced but not yet body-classified. The kit's
9
- * display unions (`ThreadCategory`, `EmailRenderCategory`, badge `MessageCategory`)
10
- * have no `uncategorized` member there is nothing to show for "not yet
11
- * classified", so it collapses to `personal`, the classifier's own fallback and
12
- * the display default that never renders a badge. Pre-migration rows that still
13
- * read `undefined` collapse the same way.
8
+ * messages that are metadata-synced but not yet body-classified. That state is
9
+ * shown as itself, not collapsed into `personal`: collapsing made an
10
+ * unclassified message identical on screen to one the classifier positively
11
+ * decided was personal, so a classification gap read as a large personal inbox
12
+ * rather than as missing work (issue #45).
13
+ *
14
+ * Pre-migration rows that read `undefined` carry no category either, so they
15
+ * map to `uncategorized` the same way.
14
16
  */
15
17
  export function toDisplayCategory(
16
18
  category: RemitImapMessageCategory | undefined,
17
19
  ): ThreadCategory {
18
- if (category === undefined || category === "uncategorized") return "personal";
20
+ if (category === undefined) return "uncategorized";
19
21
  return category;
20
22
  }