@remit/web-client 0.0.92 → 0.0.93

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.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/src/components/mail/DailyBrief.selection.test.ts +6 -2
  3. package/src/components/mail/DailyBrief.tsx +47 -110
  4. package/src/components/mail/MessageList.selection.test.ts +10 -5
  5. package/src/components/mail/MessageList.tsx +78 -82
  6. package/src/components/mail/SelectionWizardHost.tsx +648 -60
  7. package/src/components/mail/ThreadListInteraction.tsx +0 -9
  8. package/src/components/mail/organize/SearchFilterEditor.tsx +5 -1
  9. package/src/components/settings/FilterEditor.tsx +1 -1
  10. package/src/hooks/useCreateMailbox.ts +16 -4
  11. package/src/hooks/useFilters.ts +30 -1
  12. package/src/hooks/useMatchSample.ts +63 -0
  13. package/src/hooks/useRulePreview.ts +23 -3
  14. package/src/lib/mail-context.ts +0 -9
  15. package/src/lib/organize/organize-model.test.ts +110 -0
  16. package/src/lib/organize/organize-model.ts +69 -0
  17. package/src/lib/organize/rule-model.ts +2 -0
  18. package/src/lib/wizard-history.ts +15 -0
  19. package/src/routes/mail.tsx +0 -7
  20. package/src/components/mail/organize/MobileOrganizeFlow.render.test.ts +0 -45
  21. package/src/components/mail/organize/MobileOrganizeFlow.tsx +0 -157
  22. package/src/components/mail/organize/OrganizeDialog.render.test.ts +0 -37
  23. package/src/components/mail/organize/OrganizeDialog.tsx +0 -91
  24. package/src/components/mail/organize/OrganizeRuleEditor.render.test.ts +0 -498
  25. package/src/components/mail/organize/OrganizeRuleEditor.tsx +0 -285
  26. package/src/components/mail/organize/SomethingElsePanel.render.test.ts +0 -54
  27. package/src/components/mail/organize/SomethingElsePanel.tsx +0 -159
  28. package/src/components/mail/organize/smart-organize.stories.tsx +0 -342
  29. package/src/lib/organize/mobile-organize-flow.test.ts +0 -96
  30. package/src/lib/organize/mobile-organize-flow.ts +0 -73
@@ -1,157 +0,0 @@
1
- import { mailboxOperationsListMailboxesOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
2
- import type { RuleScope } from "@remit/ui";
3
- import { BottomSheet, Button } from "@remit/ui";
4
- import { useQuery } from "@tanstack/react-query";
5
- import { Loader2 } from "lucide-react";
6
- import { useEffect, useMemo, useState } from "react";
7
- import { useOrganizeWiden } from "@/hooks/useOrganizeWiden";
8
- import { getMailboxDisplayName } from "@/lib/folder-roles";
9
- import { buildMoveTargets } from "@/lib/move-targets";
10
- import {
11
- type OrganizeEntry,
12
- type OrganizeSeed,
13
- type PreviewStatus,
14
- resolveOrganizeStage,
15
- } from "@/lib/organize/mobile-organize-flow";
16
- import { OrganizeRuleEditor } from "./OrganizeRuleEditor";
17
- import { SomethingElsePanel } from "./SomethingElsePanel";
18
-
19
- /**
20
- * Map a "Something else" seed's scope onto the rule editor's scope. Only the
21
- * standing shortcut carries one; everything else lets the editor default.
22
- */
23
- const seedRuleScope = (
24
- seed: OrganizeSeed | undefined,
25
- ): RuleScope | undefined =>
26
- seed?.scope === "standing" ? "standing" : undefined;
27
-
28
- interface MobileOrganizeFlowProps {
29
- entry: OrganizeEntry;
30
- accountId: string;
31
- selectedMessageIds: string[];
32
- /**
33
- * Sender addresses of the selected messages, driving the literal fallback on
34
- * a deployment without the vector pipeline (semantic-capability.ts).
35
- */
36
- selectedSenders: string[];
37
- junkMailboxId?: string;
38
- /** Close the flow and return to the list — dismiss, "Not now", and Done all use it. */
39
- onClose: () => void;
40
- }
41
-
42
- const previewStatusOf = (
43
- isError: boolean,
44
- isPending: boolean,
45
- matchedCount: number | undefined,
46
- ): PreviewStatus => {
47
- if (isError) return "error";
48
- if (isPending) return "pending";
49
- return matchedCount !== undefined ? "success" : "idle";
50
- };
51
-
52
- /**
53
- * The guided select-similar → organize flow, the mobile home for organizing
54
- * (issue #211). Entered from the selection sheet, it widens the selection once
55
- * with the read-only matcher (POST /organize/preview), shows a brief widening
56
- * state, and renders the organize sentence inside a bottom sheet on that
57
- * widened set — the same {@link OrganizePanel} the desktop dialog uses, so the
58
- * two never drift. "Something else" collects a folder/scope seed first; a widen
59
- * that matches nothing falls back to organizing the selection. Desktop keeps
60
- * its `OrganizeDialog` — this is the touch surface only.
61
- */
62
- export function MobileOrganizeFlow({
63
- entry,
64
- accountId,
65
- selectedMessageIds,
66
- selectedSenders,
67
- junkMailboxId,
68
- onClose,
69
- }: MobileOrganizeFlowProps) {
70
- const anchorMessageId = selectedMessageIds[0];
71
- const [seed, setSeed] = useState<OrganizeSeed | undefined>();
72
-
73
- const {
74
- preview,
75
- matchedCount,
76
- semanticUnavailable,
77
- senders,
78
- isPending,
79
- isError,
80
- error,
81
- } = useOrganizeWiden(accountId, anchorMessageId, selectedSenders);
82
-
83
- useEffect(() => {
84
- preview();
85
- }, [preview]);
86
-
87
- const { data: mailboxesData } = useQuery({
88
- ...mailboxOperationsListMailboxesOptions({ path: { accountId } }),
89
- staleTime: Number.POSITIVE_INFINITY,
90
- });
91
-
92
- const folderOptions = useMemo(
93
- () =>
94
- buildMoveTargets(mailboxesData?.items ?? []).map((mailbox) => ({
95
- id: mailbox.mailboxId,
96
- label: getMailboxDisplayName(mailbox.fullPath),
97
- })),
98
- [mailboxesData?.items],
99
- );
100
-
101
- const stage = resolveOrganizeStage({
102
- entry,
103
- hasSeed: seed !== undefined,
104
- previewStatus: previewStatusOf(isError, isPending, matchedCount),
105
- matchedCount,
106
- });
107
-
108
- return (
109
- <BottomSheet open onClose={onClose} dismissLabel="Dismiss organize">
110
- {stage.kind === "something-else" && (
111
- <SomethingElsePanel
112
- folderOptions={folderOptions}
113
- junkMailboxId={junkMailboxId}
114
- onSeed={setSeed}
115
- />
116
- )}
117
-
118
- {stage.kind === "widening" && <WideningState />}
119
-
120
- {stage.kind === "error" && (
121
- <div className="flex flex-col items-center gap-3 px-5 py-10 text-center">
122
- <p className="text-sm font-medium text-danger">
123
- Couldn't find similar messages
124
- </p>
125
- <p className="max-w-xs text-xs text-fg-muted">
126
- {error instanceof Error ? error.message : "Please try again."}
127
- </p>
128
- <Button variant="ghost" onClick={onClose} className="mt-2">
129
- Close
130
- </Button>
131
- </div>
132
- )}
133
-
134
- {stage.kind === "organize" && (
135
- <OrganizeRuleEditor
136
- accountId={accountId}
137
- selectedMessageIds={selectedMessageIds}
138
- seedCount={stage.matchedCount}
139
- seedScope={seedRuleScope(seed)}
140
- seedMailboxId={seed?.moveMailboxId}
141
- semanticUnavailable={semanticUnavailable}
142
- senders={senders}
143
- onClose={onClose}
144
- />
145
- )}
146
- </BottomSheet>
147
- );
148
- }
149
-
150
- function WideningState() {
151
- return (
152
- <div className="flex flex-col items-center gap-3 px-5 py-10 text-center">
153
- <Loader2 className="size-8 animate-spin text-accent-2" />
154
- <p className="text-sm font-medium text-fg">Finding similar messages…</p>
155
- </div>
156
- );
157
- }
@@ -1,37 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
- import React, { createElement } from "react";
5
- import { renderToString } from "react-dom/server";
6
- import { OrganizeDialog } from "./OrganizeDialog";
7
-
8
- // The node test loader transpiles remit-ui's `.tsx` with the classic JSX
9
- // runtime, which references a global `React`. Vite uses the automatic runtime,
10
- // so this shim only exists for the SSR test harness.
11
- (globalThis as { React?: typeof React }).React = React;
12
-
13
- const render = (open: boolean) =>
14
- renderToString(
15
- createElement(
16
- QueryClientProvider,
17
- { client: new QueryClient() },
18
- createElement(OrganizeDialog, {
19
- open,
20
- accountId: "acc-1",
21
- selectedMessageIds: ["msg-1", "msg-2"],
22
- selectedSenders: ["npm@github.com"],
23
- onClose: () => undefined,
24
- }),
25
- ) as never,
26
- );
27
-
28
- describe("OrganizeDialog", () => {
29
- it("renders nothing when closed", () => {
30
- assert.equal(render(false), "");
31
- });
32
-
33
- it("shows the widen step while the preview is in flight", () => {
34
- const html = render(true);
35
- assert.match(html, /Finding similar messages/);
36
- });
37
- });
@@ -1,91 +0,0 @@
1
- import { Button, Dialog } from "@remit/ui";
2
- import { Loader2 } from "lucide-react";
3
- import { useEffect } from "react";
4
- import { useOrganizeWiden } from "@/hooks/useOrganizeWiden";
5
- import { OrganizeRuleEditor } from "./OrganizeRuleEditor";
6
-
7
- interface OrganizeDialogProps {
8
- open: boolean;
9
- accountId: string;
10
- selectedMessageIds: string[];
11
- /**
12
- * Sender addresses of the selected messages, driving the literal fallback on
13
- * a deployment without the vector pipeline (semantic-capability.ts).
14
- */
15
- selectedSenders: string[];
16
- onClose: () => void;
17
- }
18
-
19
- /**
20
- * Smart-organize flow entered from the selection toolbar. Widens the selection
21
- * once (POST /organize/preview) to seed the rule, then hands off to the chip
22
- * editor (RFC 038 D1), which counts and commits over the same endpoints. The
23
- * widen is only the opening count; the editor re-previews every edit.
24
- */
25
- export function OrganizeDialog({
26
- open,
27
- accountId,
28
- selectedMessageIds,
29
- selectedSenders,
30
- onClose,
31
- }: OrganizeDialogProps) {
32
- const anchorMessageId = selectedMessageIds[0];
33
- const {
34
- preview,
35
- reset,
36
- matchedCount,
37
- semanticUnavailable,
38
- senders,
39
- isPending,
40
- isError,
41
- error,
42
- } = useOrganizeWiden(accountId, anchorMessageId, selectedSenders);
43
-
44
- useEffect(() => {
45
- if (!open) return;
46
- preview();
47
- }, [open, preview]);
48
-
49
- const handleClose = () => {
50
- reset();
51
- onClose();
52
- };
53
-
54
- if (!open) return null;
55
-
56
- return (
57
- <Dialog open={open} onClose={handleClose} title="Filter rule">
58
- {isPending || matchedCount === undefined ? (
59
- isError ? (
60
- <div className="flex flex-col items-center gap-3 px-5 py-8 text-center">
61
- <p className="text-sm font-medium text-danger">
62
- Couldn't find similar messages
63
- </p>
64
- <p className="max-w-xs text-xs text-fg-muted">
65
- {error instanceof Error ? error.message : "Please try again."}
66
- </p>
67
- <Button variant="ghost" onClick={handleClose} className="mt-2">
68
- Close
69
- </Button>
70
- </div>
71
- ) : (
72
- <div className="flex flex-col items-center gap-3 px-5 py-10 text-center">
73
- <Loader2 className="size-8 animate-spin text-accent-2" />
74
- <p className="text-sm font-medium text-fg">
75
- Finding similar messages…
76
- </p>
77
- </div>
78
- )
79
- ) : (
80
- <OrganizeRuleEditor
81
- accountId={accountId}
82
- selectedMessageIds={selectedMessageIds}
83
- seedCount={matchedCount}
84
- semanticUnavailable={semanticUnavailable}
85
- senders={senders}
86
- onClose={handleClose}
87
- />
88
- )}
89
- </Dialog>
90
- );
91
- }