@remit/web-client 0.0.148 → 0.0.149

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.148",
3
+ "version": "0.0.149",
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": {
@@ -33,7 +33,7 @@ const baseData: IntelligenceData = {
33
33
  dkimDomain: "example.com",
34
34
  summary: "This message was signed by example.com.",
35
35
  },
36
- category: { value: "Personal" },
36
+ category: { value: "personal" },
37
37
  similar: [],
38
38
  };
39
39
 
@@ -6,6 +6,7 @@ import type {
6
6
  } from "@remit/api-http-client/types.gen.ts";
7
7
  import {
8
8
  buildAuthenticityIntel,
9
+ buildCategoryIntel,
9
10
  buildSenderIntel,
10
11
  } from "./useIntelligenceData.js";
11
12
 
@@ -49,6 +50,45 @@ function makeAddress(
49
50
  } as RemitImapAddressResponse;
50
51
  }
51
52
 
53
+ // The generated union has a member for every category this build knows, so a
54
+ // value outside it cannot be written as one — which is the whole point: it
55
+ // arrives as JSON from a server that classifies into more than this client can.
56
+ function threadCategorizedAs(category: string): RemitImapThreadMessageResponse {
57
+ return { ...makeThread(), category } as RemitImapThreadMessageResponse;
58
+ }
59
+
60
+ describe("buildCategoryIntel", () => {
61
+ test("carries a category the client knows through unchanged", () => {
62
+ const result = buildCategoryIntel(
63
+ makeThread({ category: "newsletter" }),
64
+ undefined,
65
+ );
66
+ assert.equal(result.value, "newsletter");
67
+ });
68
+
69
+ test("falls back to personal when the row carries no category", () => {
70
+ const result = buildCategoryIntel(makeThread(), undefined);
71
+ assert.equal(result.value, "personal");
72
+ });
73
+
74
+ test("resolves a category the client does not know to uncategorized", () => {
75
+ const result = buildCategoryIntel(
76
+ threadCategorizedAs("invoice"),
77
+ undefined,
78
+ );
79
+ assert.equal(result.value, "uncategorized");
80
+ });
81
+
82
+ test("marks the category overridden when the sender carries a different one", () => {
83
+ const thread = makeThread({ category: "personal" });
84
+ const address = makeAddress({
85
+ flags: { category: { value: "newsletter" } },
86
+ } as Partial<RemitImapAddressResponse>);
87
+ assert.equal(buildCategoryIntel(thread, address).overridden, true);
88
+ assert.equal(buildCategoryIntel(thread, undefined).overridden, false);
89
+ });
90
+ });
91
+
52
92
  describe("buildSenderIntel", () => {
53
93
  describe("counter wiring — counters present", () => {
54
94
  test("surfaces inboundCount from address", () => {
@@ -15,6 +15,7 @@ import type {
15
15
  SenderIntel,
16
16
  SimilarMessageIntel,
17
17
  } from "@remit/ui";
18
+ import { isThreadCategory } from "@remit/ui";
18
19
  import { useQuery } from "@tanstack/react-query";
19
20
  import { useMemo } from "react";
20
21
  import { isServerError } from "@/lib/error-classifier";
@@ -56,6 +57,28 @@ const NO_SIGNAL_SUMMARY =
56
57
  const UNREADABLE_SENDER_SUMMARY =
57
58
  "We couldn't read this sender's address, so we can't confirm who really sent this message.";
58
59
 
60
+ /**
61
+ * Build category intel from the thread message and address lookup.
62
+ *
63
+ * The row's category is a string the API chose, so it is narrowed rather than
64
+ * asserted: a value this build has no entry for — a category a newer server
65
+ * classifies into — resolves to `uncategorized`, which is the taxonomy's own
66
+ * name for "no classification this client can act on" and renders a readable
67
+ * neutral chip. Asserting instead would leave the chip with no tone at all.
68
+ */
69
+ export function buildCategoryIntel(
70
+ thread: RemitImapThreadMessageResponse,
71
+ address: RemitImapAddressResponse | undefined,
72
+ ): IntelligenceData["category"] {
73
+ const served = thread.category ?? "personal";
74
+ return {
75
+ value: isThreadCategory(served) ? served : "uncategorized",
76
+ overridden:
77
+ address?.flags?.category?.value != null &&
78
+ address.flags.category.value !== thread.category,
79
+ };
80
+ }
81
+
59
82
  /**
60
83
  * Build sender intel from the thread message and address lookup.
61
84
  *
@@ -357,12 +380,7 @@ export function useIntelligenceData(
357
380
 
358
381
  const authenticity = buildAuthenticityIntel(thread, similar.length);
359
382
 
360
- const category = {
361
- value: thread.category ?? "personal",
362
- overridden:
363
- address?.flags?.category?.value != null &&
364
- address.flags.category.value !== thread.category,
365
- };
383
+ const category = buildCategoryIntel(thread, address);
366
384
 
367
385
  const flags = buildSenderFlags(address);
368
386