@remit/web-client 0.0.58 → 0.0.60

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.58",
3
+ "version": "0.0.60",
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": {
@@ -56,8 +56,14 @@ export function MobileOrganizeFlow({
56
56
  const anchorMessageId = selectedMessageIds[0];
57
57
  const [seed, setSeed] = useState<OrganizeSeed | undefined>();
58
58
 
59
- const { preview, matchedCount, isPending, isError, error } =
60
- useOrganizePreview(accountId);
59
+ const {
60
+ preview,
61
+ matchedCount,
62
+ semanticUnavailable,
63
+ isPending,
64
+ isError,
65
+ error,
66
+ } = useOrganizePreview(accountId);
61
67
 
62
68
  useEffect(() => {
63
69
  if (!anchorMessageId) return;
@@ -121,6 +127,7 @@ export function MobileOrganizeFlow({
121
127
  initialScope={seed?.scope}
122
128
  seedMailboxId={seed?.moveMailboxId}
123
129
  fallback={stage.fallback}
130
+ semanticUnavailable={semanticUnavailable}
124
131
  onClose={onClose}
125
132
  />
126
133
  )}
@@ -26,8 +26,15 @@ export function OrganizeDialog({
26
26
  onClose,
27
27
  }: OrganizeDialogProps) {
28
28
  const anchorMessageId = selectedMessageIds[0];
29
- const { preview, reset, matchedCount, isPending, isError, error } =
30
- useOrganizePreview(accountId);
29
+ const {
30
+ preview,
31
+ reset,
32
+ matchedCount,
33
+ semanticUnavailable,
34
+ isPending,
35
+ isError,
36
+ error,
37
+ } = useOrganizePreview(accountId);
31
38
 
32
39
  useEffect(() => {
33
40
  if (!open || !anchorMessageId) return;
@@ -75,6 +82,7 @@ export function OrganizeDialog({
75
82
  selectedMessageIds={selectedMessageIds}
76
83
  anchorMessageId={anchorMessageId}
77
84
  matchedCount={matchedCount}
85
+ semanticUnavailable={semanticUnavailable}
78
86
  onClose={handleClose}
79
87
  />
80
88
  )}
@@ -62,6 +62,12 @@ describe("OrganizePanel", () => {
62
62
  assert.match(html, /organizing just your 2 selected/);
63
63
  });
64
64
 
65
+ it("names the missing vector pipeline and organizes just the selection when the widen is unavailable (#226/#201)", () => {
66
+ const html = render({ matchedCount: 0, semanticUnavailable: true });
67
+ assert.match(html, /available on this server/);
68
+ assert.match(html, /organizing just your 2 selected/);
69
+ });
70
+
65
71
  it("pre-selects the seeded scope (a 'Something else' shortcut seeds the sentence)", () => {
66
72
  const html = render({ initialScope: "standing" });
67
73
  // The standing scope's "Always keep" phrasing only renders when it is active.
@@ -42,6 +42,12 @@ interface OrganizePanelProps {
42
42
  * `just-these`.
43
43
  */
44
44
  fallback?: boolean;
45
+ /**
46
+ * The server ran no semantic widen because this deployment ships no vector
47
+ * pipeline. Like {@link fallback} it organizes just the selection, but the
48
+ * heading names the real reason rather than "no similar found".
49
+ */
50
+ semanticUnavailable?: boolean;
45
51
  onClose: () => void;
46
52
  }
47
53
 
@@ -77,10 +83,14 @@ export function OrganizePanel({
77
83
  initialScope,
78
84
  seedMailboxId,
79
85
  fallback = false,
86
+ semanticUnavailable = false,
80
87
  onClose,
81
88
  }: OrganizePanelProps) {
89
+ // A missing widen and an empty widen both organize just the selection; only
90
+ // the heading distinguishes them.
91
+ const noWiden = fallback || semanticUnavailable;
82
92
  const [scope, setScope] = useState<OrganizeScope>(
83
- initialScope ?? (fallback ? "just-these" : "all-like-these"),
93
+ initialScope ?? (noWiden ? "just-these" : "all-like-these"),
84
94
  );
85
95
  const [moveMailboxId, setMoveMailboxId] = useState<string>(
86
96
  seedMailboxId ?? "",
@@ -178,11 +188,13 @@ export function OrganizePanel({
178
188
  <div className="border-b border-line px-5 py-3">
179
189
  <h2 className="text-sm font-semibold text-fg">Organize</h2>
180
190
  <p className="mt-0.5 text-xs text-fg-muted">
181
- {fallback
182
- ? `No similar messages found — organizing just your ${selectionCount} selected.`
183
- : `${matchedCount} similar message${matchedCount === 1 ? "" : "s"} found${
184
- selectionCount > 0 ? ` from ${selectionCount} selected` : ""
185
- }.`}
191
+ {semanticUnavailable
192
+ ? `Finding similar mail isn't available on this server — organizing just your ${selectionCount} selected.`
193
+ : fallback
194
+ ? `No similar messages found organizing just your ${selectionCount} selected.`
195
+ : `${matchedCount} similar message${matchedCount === 1 ? "" : "s"} found${
196
+ selectionCount > 0 ? ` from ${selectionCount} selected` : ""
197
+ }.`}
186
198
  </p>
187
199
  </div>
188
200
 
@@ -183,6 +183,28 @@ export const NoSimilarFound: Story = {
183
183
  ),
184
184
  };
185
185
 
186
+ /**
187
+ * The deployment ships no vector pipeline, so the server ran no semantic widen —
188
+ * the sentence organizes just the selection and the heading names the real
189
+ * reason (#226/#201).
190
+ */
191
+ export const SemanticUnavailable: Story = {
192
+ name: "Semantic Unavailable",
193
+ render: () => (
194
+ <SheetStage>
195
+ <OrganizePanel
196
+ accountId={ACCOUNT_ID}
197
+ mailboxId="mbx-inbox"
198
+ selectedMessageIds={["msg-1", "msg-2"]}
199
+ anchorMessageId="msg-1"
200
+ matchedCount={0}
201
+ semanticUnavailable
202
+ onClose={() => undefined}
203
+ />
204
+ </SheetStage>
205
+ ),
206
+ };
207
+
186
208
  /** The "Something else" fallback: shortcuts derived from folders + an input. */
187
209
  export const SomethingElse: Story = {
188
210
  render: () => (
@@ -33,6 +33,9 @@ export const useOrganizePreview = (accountId: string | undefined) => {
33
33
  reset: mutation.reset,
34
34
  matchedCount: mutation.data?.matchedCount,
35
35
  messageIds: mutation.data?.messageIds,
36
+ // True when the server ran no semantic widen because this deployment
37
+ // ships no vector pipeline — the matched set is literal-only (or empty).
38
+ semanticUnavailable: mutation.data?.semanticUnavailable ?? false,
36
39
  isPending: mutation.isPending,
37
40
  isError: mutation.isError,
38
41
  error: mutation.error,
@@ -17,15 +17,15 @@ const FROM_PLACEMENT_LABEL: Record<string, string> = {
17
17
  * (verdict/confidence/dryRun never surface) — mirrors the no-jargon precedent
18
18
  * in `rescue-candidates.ts`.
19
19
  */
20
- export const autoMovedLabel = (fromPlacement: string): string =>
21
- `Moved from ${FROM_PLACEMENT_LABEL[fromPlacement] ?? "another folder"} by Remit`;
20
+ export const autoMovedLabel = (fromPlacement: string | undefined): string =>
21
+ `Moved from ${FROM_PLACEMENT_LABEL[fromPlacement ?? ""] ?? "another folder"} by Remit`;
22
22
 
23
23
  /**
24
24
  * The mailbox the verdict's `action` implies as the destination — where the
25
25
  * message should currently sit for the move to still be "in effect".
26
26
  */
27
27
  const impliedDestinationMailboxId = (
28
- action: string,
28
+ action: string | undefined,
29
29
  mailboxes: AutoMovedRoleMailboxes,
30
30
  ): string | undefined => {
31
31
  if (action === PlacementAction.MoveToInbox) return mailboxes.inboxMailboxId;
@@ -59,7 +59,7 @@ export const isAutoMoveInEffect = (
59
59
  * account has no mailbox appointed to that role.
60
60
  */
61
61
  export const resolveUndoTargetMailboxId = (
62
- fromPlacement: string,
62
+ fromPlacement: string | undefined,
63
63
  mailboxes: AutoMovedRoleMailboxes,
64
64
  ): string | undefined => {
65
65
  if (fromPlacement === "inbox") return mailboxes.inboxMailboxId;