@remit/web-client 0.0.5 → 0.0.7
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/harness/build.mjs +15 -3
- package/harness/vite-preset.ts +2 -0
- package/package.json +1 -1
- package/src/components/mail/FlaggedList.tsx +2 -2
- package/src/components/mail/MailboxPane.tsx +5 -3
- package/src/components/mail/MessageListItem.tsx +1 -2
- package/src/components/mail/MessageToolbar.render.test.ts +1 -1
- package/src/components/mail/MessageToolbar.tsx +1 -1
- package/src/hooks/useToggleStar.test.ts +57 -0
- package/src/hooks/useToggleStar.ts +38 -8
- package/src/lib/brief.test.ts +54 -0
- package/src/lib/brief.ts +1 -2
- package/src/lib/keymap.ts +1 -1
- package/src/routes/mail/flagged.tsx +1 -1
- package/vite.base.ts +15 -1
package/harness/build.mjs
CHANGED
|
@@ -67,15 +67,27 @@ const run = async () => {
|
|
|
67
67
|
cpSync(join(harnessDir, "index.html"), join(root, "index.html"));
|
|
68
68
|
writeFileSync(join(root, "entry.tsx"), entrySource(provider));
|
|
69
69
|
|
|
70
|
+
const preset = webClientPreset();
|
|
70
71
|
try {
|
|
71
72
|
await build({
|
|
73
|
+
...preset,
|
|
72
74
|
root,
|
|
73
75
|
publicDir: join(packageDir, "public"),
|
|
74
|
-
build: {
|
|
75
|
-
|
|
76
|
+
build: {
|
|
77
|
+
...preset.build,
|
|
78
|
+
outDir: resolve(packageDir, out),
|
|
79
|
+
emptyOutDir: true,
|
|
80
|
+
},
|
|
76
81
|
});
|
|
77
82
|
} finally {
|
|
78
|
-
|
|
83
|
+
// Never let cleanup replace the build's own failure. Removing the root is
|
|
84
|
+
// best-effort housekeeping on a throwaway directory; a build error is the
|
|
85
|
+
// thing the caller needs to see.
|
|
86
|
+
try {
|
|
87
|
+
rmSync(root, { recursive: true, force: true });
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn(`Could not remove ${root}: ${error.message}`);
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
console.log(`Built web client (auth: ${auth}) to ${resolve(packageDir, out)}`);
|
package/harness/vite-preset.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { fileURLToPath } from "node:url";
|
|
|
2
2
|
import type { UserConfig } from "vite";
|
|
3
3
|
import {
|
|
4
4
|
webClientAlias,
|
|
5
|
+
webClientBuild,
|
|
5
6
|
webClientDefine,
|
|
6
7
|
webClientPlugins,
|
|
7
8
|
} from "../vite.base.ts";
|
|
@@ -26,4 +27,5 @@ export const webClientPreset = (): UserConfig => ({
|
|
|
26
27
|
}),
|
|
27
28
|
resolve: { alias: webClientAlias() },
|
|
28
29
|
define: webClientDefine(),
|
|
30
|
+
build: webClientBuild(),
|
|
29
31
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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": {
|
|
@@ -137,7 +137,7 @@ export function FlaggedList({
|
|
|
137
137
|
|
|
138
138
|
return (
|
|
139
139
|
<MailViewChrome
|
|
140
|
-
title="
|
|
140
|
+
title="Starred"
|
|
141
141
|
unreadCount={unreadCount}
|
|
142
142
|
preset={preset}
|
|
143
143
|
selectedCategory={selectedCategory}
|
|
@@ -150,7 +150,7 @@ export function FlaggedList({
|
|
|
150
150
|
onSelectSearchResult={(result) => onSelectMessage?.(result.id)}
|
|
151
151
|
>
|
|
152
152
|
<MessageListPane
|
|
153
|
-
listTitle="
|
|
153
|
+
listTitle="Starred"
|
|
154
154
|
sections={[{ id: "flagged", threads: rows }]}
|
|
155
155
|
flatList
|
|
156
156
|
hideHeader
|
|
@@ -110,8 +110,10 @@ import { useTelemetry } from "@/lib/telemetry-context";
|
|
|
110
110
|
import { MailViewChrome } from "./MailViewChrome";
|
|
111
111
|
|
|
112
112
|
/* ------------------------------------------------------------------ */
|
|
113
|
-
/* Inbox filter predicates — the inbox preset offers Unread /
|
|
114
|
-
Has attachment (never accounts; an inbox is one account already).
|
|
113
|
+
/* Inbox filter predicates — the inbox preset offers Unread / Starred /
|
|
114
|
+
Has attachment (never accounts; an inbox is one account already).
|
|
115
|
+
The `flagged` id is the wire name for IMAP \Flagged; the label is
|
|
116
|
+
"Starred". */
|
|
115
117
|
/* ------------------------------------------------------------------ */
|
|
116
118
|
|
|
117
119
|
const INBOX_FILTER_PREDICATES: Record<
|
|
@@ -119,7 +121,7 @@ const INBOX_FILTER_PREDICATES: Record<
|
|
|
119
121
|
(t: RemitImapThreadMessageResponse) => boolean
|
|
120
122
|
> = {
|
|
121
123
|
unread: (t) => !t.isRead,
|
|
122
|
-
flagged: (t) => t.
|
|
124
|
+
flagged: (t) => t.hasStars === true,
|
|
123
125
|
attachment: (t) => Boolean(t.hasAttachment),
|
|
124
126
|
};
|
|
125
127
|
|
|
@@ -80,8 +80,7 @@ const toThreadRowData = (
|
|
|
80
80
|
timeLabel: formatEmailDate(thread.sentDate),
|
|
81
81
|
isRead: thread.isRead,
|
|
82
82
|
hasAttachment: thread.hasAttachment,
|
|
83
|
-
starred:
|
|
84
|
-
thread.star != null && thread.star !== "none" && thread.hasStars === true,
|
|
83
|
+
starred: thread.hasStars === true,
|
|
85
84
|
trust: thread.senderTrust as SenderTrustLevel,
|
|
86
85
|
category: toDisplayCategory(thread.category),
|
|
87
86
|
messageCount,
|
|
@@ -50,7 +50,7 @@ describe("MailActionToolbar never disables its action buttons (#799)", () => {
|
|
|
50
50
|
it("keeps the action buttons present (reply/flag) so they stay pressable", () => {
|
|
51
51
|
const html = render({ hasThread: false });
|
|
52
52
|
assert.match(html, /aria-label="Reply"/);
|
|
53
|
-
assert.match(html, /aria-label="
|
|
53
|
+
assert.match(html, /aria-label="Star"/);
|
|
54
54
|
// No archive verb — Remit is IMAP-backed (move-to-folder is the equivalent).
|
|
55
55
|
assert.doesNotMatch(html, /aria-label="Archive"/);
|
|
56
56
|
});
|
|
@@ -102,7 +102,7 @@ export const MessageToolbar = ({
|
|
|
102
102
|
replyAllTitle={`Reply all ${tooltipForAction("replyAll")}`}
|
|
103
103
|
forwardTitle={`Forward ${tooltipForAction("forward")}`}
|
|
104
104
|
deleteTitle={`Move to Trash ${tooltipForAction("delete")}`}
|
|
105
|
-
flagTitle={`
|
|
105
|
+
flagTitle={`Star ${tooltipForAction("toggleStar")}`}
|
|
106
106
|
onReply={onReply}
|
|
107
107
|
onReplyAll={onReplyAll}
|
|
108
108
|
onForward={onForward}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
4
|
+
import { toggleStarsInItems } from "./useToggleStar.js";
|
|
5
|
+
|
|
6
|
+
const make = (
|
|
7
|
+
messageId: string,
|
|
8
|
+
hasStars: boolean,
|
|
9
|
+
): RemitImapThreadMessageResponse => ({
|
|
10
|
+
senderTrust: "unknown",
|
|
11
|
+
threadId: "t1",
|
|
12
|
+
threadMessageId: `tm-${messageId}`,
|
|
13
|
+
messageId,
|
|
14
|
+
accountConfigId: "cfg_1",
|
|
15
|
+
mailboxId: "mb1",
|
|
16
|
+
subject: "s",
|
|
17
|
+
fromName: "n",
|
|
18
|
+
fromEmail: "e",
|
|
19
|
+
sentDate: 1767225600,
|
|
20
|
+
snippet: "",
|
|
21
|
+
isRead: false,
|
|
22
|
+
isDeleted: false,
|
|
23
|
+
hasAttachment: false,
|
|
24
|
+
star: "none",
|
|
25
|
+
hasStars,
|
|
26
|
+
createdAt: 0,
|
|
27
|
+
updatedAt: 0,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("toggleStarsInItems", () => {
|
|
31
|
+
test("stars only the target message", () => {
|
|
32
|
+
const items = [make("m1", false), make("m2", false)];
|
|
33
|
+
const got = toggleStarsInItems(items, "m1", true);
|
|
34
|
+
assert.deepStrictEqual(
|
|
35
|
+
got.map((i) => [i.messageId, i.hasStars]),
|
|
36
|
+
[
|
|
37
|
+
["m1", true],
|
|
38
|
+
["m2", false],
|
|
39
|
+
],
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("unstars the target message", () => {
|
|
44
|
+
const items = [make("m1", true)];
|
|
45
|
+
const got = toggleStarsInItems(items, "m1", false);
|
|
46
|
+
assert.strictEqual(got[0]?.hasStars, false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("leaves the list untouched when the message is absent", () => {
|
|
50
|
+
const items = [make("m1", false)];
|
|
51
|
+
const got = toggleStarsInItems(items, "missing", true);
|
|
52
|
+
assert.deepStrictEqual(
|
|
53
|
+
got.map((i) => i.hasStars),
|
|
54
|
+
[false],
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
threadDetailOperationsListThreadMessagesQueryKey,
|
|
4
4
|
threadOperationsListThreadsQueryKey,
|
|
5
5
|
threadOperationsSearchThreadsQueryKey,
|
|
6
|
+
unifiedThreadOperationsListAllThreadsQueryKey,
|
|
6
7
|
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
7
8
|
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
8
9
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
@@ -38,11 +39,13 @@ interface ToggleStarContext {
|
|
|
38
39
|
threadMessagesPrefix: readonly unknown[];
|
|
39
40
|
threadsListPrefix: readonly unknown[];
|
|
40
41
|
threadsSearchPrefix: readonly unknown[];
|
|
42
|
+
unifiedThreadsPrefix: readonly unknown[];
|
|
41
43
|
previousThreadMessages: SnapshotEntry<ThreadMessagesData>[];
|
|
42
44
|
previousThreadsList: SnapshotEntry<ThreadsListData>[];
|
|
45
|
+
previousUnifiedThreads: SnapshotEntry<ThreadMessagesData>[];
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
const toggleStarsInItems = (
|
|
48
|
+
export const toggleStarsInItems = (
|
|
46
49
|
items: RemitImapThreadMessageResponse[],
|
|
47
50
|
messageId: string,
|
|
48
51
|
nextStarred: boolean,
|
|
@@ -78,11 +81,18 @@ export const useToggleStar = ({
|
|
|
78
81
|
const threadsSearchPrefix = threadOperationsSearchThreadsQueryKey({
|
|
79
82
|
path: { mailboxId },
|
|
80
83
|
});
|
|
84
|
+
// The unified cross-account listing backs the daily brief and the
|
|
85
|
+
// Starred mailbox, so a star toggled from an inbox has to land there
|
|
86
|
+
// too — without this the starred view keeps serving a stale page for
|
|
87
|
+
// its whole staleTime and the message never appears.
|
|
88
|
+
const unifiedThreadsPrefix =
|
|
89
|
+
unifiedThreadOperationsListAllThreadsQueryKey();
|
|
81
90
|
|
|
82
91
|
await Promise.all([
|
|
83
92
|
queryClient.cancelQueries({ queryKey: threadMessagesPrefix }),
|
|
84
93
|
queryClient.cancelQueries({ queryKey: threadsListPrefix }),
|
|
85
94
|
queryClient.cancelQueries({ queryKey: threadsSearchPrefix }),
|
|
95
|
+
queryClient.cancelQueries({ queryKey: unifiedThreadsPrefix }),
|
|
86
96
|
]);
|
|
87
97
|
|
|
88
98
|
const previousThreadMessages = queryClient
|
|
@@ -106,15 +116,29 @@ export const useToggleStar = ({
|
|
|
106
116
|
)
|
|
107
117
|
.map(([queryKey, data]) => ({ queryKey, data }));
|
|
108
118
|
|
|
119
|
+
const previousUnifiedThreads = queryClient
|
|
120
|
+
.getQueriesData<ThreadMessagesData>({ queryKey: unifiedThreadsPrefix })
|
|
121
|
+
.filter(
|
|
122
|
+
(entry): entry is [readonly unknown[], ThreadMessagesData] =>
|
|
123
|
+
entry[1] !== undefined,
|
|
124
|
+
)
|
|
125
|
+
.map(([queryKey, data]) => ({ queryKey, data }));
|
|
126
|
+
|
|
127
|
+
const patchItemsData = (old: ThreadMessagesData | undefined) => {
|
|
128
|
+
if (!old) return old;
|
|
129
|
+
return {
|
|
130
|
+
...old,
|
|
131
|
+
items: toggleStarsInItems(old.items, messageId, nextStarred),
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
109
135
|
queryClient.setQueriesData<ThreadMessagesData>(
|
|
110
136
|
{ queryKey: threadMessagesPrefix },
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
117
|
-
},
|
|
137
|
+
patchItemsData,
|
|
138
|
+
);
|
|
139
|
+
queryClient.setQueriesData<ThreadMessagesData>(
|
|
140
|
+
{ queryKey: unifiedThreadsPrefix },
|
|
141
|
+
patchItemsData,
|
|
118
142
|
);
|
|
119
143
|
|
|
120
144
|
const patchListData = (old: ThreadsListData | undefined) => {
|
|
@@ -141,8 +165,10 @@ export const useToggleStar = ({
|
|
|
141
165
|
threadMessagesPrefix,
|
|
142
166
|
threadsListPrefix,
|
|
143
167
|
threadsSearchPrefix,
|
|
168
|
+
unifiedThreadsPrefix,
|
|
144
169
|
previousThreadMessages,
|
|
145
170
|
previousThreadsList,
|
|
171
|
+
previousUnifiedThreads,
|
|
146
172
|
};
|
|
147
173
|
},
|
|
148
174
|
onError: (err, vars, context) => {
|
|
@@ -153,6 +179,9 @@ export const useToggleStar = ({
|
|
|
153
179
|
for (const entry of context.previousThreadsList) {
|
|
154
180
|
queryClient.setQueryData(entry.queryKey, entry.data);
|
|
155
181
|
}
|
|
182
|
+
for (const entry of context.previousUnifiedThreads) {
|
|
183
|
+
queryClient.setQueryData(entry.queryKey, entry.data);
|
|
184
|
+
}
|
|
156
185
|
}
|
|
157
186
|
const nextStarred = vars.body.isStarred ?? false;
|
|
158
187
|
pushError({
|
|
@@ -167,6 +196,7 @@ export const useToggleStar = ({
|
|
|
167
196
|
queryClient.invalidateQueries({ queryKey: context.threadMessagesPrefix });
|
|
168
197
|
queryClient.invalidateQueries({ queryKey: context.threadsListPrefix });
|
|
169
198
|
queryClient.invalidateQueries({ queryKey: context.threadsSearchPrefix });
|
|
199
|
+
queryClient.invalidateQueries({ queryKey: context.unifiedThreadsPrefix });
|
|
170
200
|
},
|
|
171
201
|
});
|
|
172
202
|
|
package/src/lib/brief.test.ts
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
+
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
3
4
|
import type { ThreadRowData } from "@remit/ui";
|
|
4
5
|
import {
|
|
5
6
|
groupBriefSections,
|
|
6
7
|
matchesBriefSearch,
|
|
7
8
|
matchesSearchTokens,
|
|
9
|
+
toThreadRowData,
|
|
8
10
|
} from "./brief.js";
|
|
9
11
|
import type { SearchToken } from "./search-tokens.js";
|
|
10
12
|
|
|
13
|
+
function threadResponse(
|
|
14
|
+
overrides: Partial<RemitImapThreadMessageResponse> = {},
|
|
15
|
+
): RemitImapThreadMessageResponse {
|
|
16
|
+
return {
|
|
17
|
+
threadId: "t1",
|
|
18
|
+
threadMessageId: "tm1",
|
|
19
|
+
messageId: "m1",
|
|
20
|
+
accountConfigId: "cfg_1",
|
|
21
|
+
mailboxId: "mb1",
|
|
22
|
+
fromName: "Sender",
|
|
23
|
+
fromEmail: "sender@example.com",
|
|
24
|
+
subject: "Subject",
|
|
25
|
+
snippet: "Snippet",
|
|
26
|
+
sentDate: 1767225600,
|
|
27
|
+
isRead: false,
|
|
28
|
+
isDeleted: false,
|
|
29
|
+
hasAttachment: false,
|
|
30
|
+
hasStars: false,
|
|
31
|
+
star: "none",
|
|
32
|
+
senderTrust: "unknown",
|
|
33
|
+
createdAt: 0,
|
|
34
|
+
updatedAt: 0,
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
11
39
|
function row(
|
|
12
40
|
overrides: Partial<ThreadRowData> & Pick<ThreadRowData, "id">,
|
|
13
41
|
): ThreadRowData {
|
|
@@ -25,6 +53,32 @@ function row(
|
|
|
25
53
|
};
|
|
26
54
|
}
|
|
27
55
|
|
|
56
|
+
describe("toThreadRowData", () => {
|
|
57
|
+
// `star` is a colour, and it defaults to the "none" sentinel — starring a
|
|
58
|
+
// message only flips `hasStars`. Reading the colour to decide starredness
|
|
59
|
+
// made every row unstarred and left the Starred mailbox permanently empty.
|
|
60
|
+
test("a message with hasStars and the default star colour is starred", () => {
|
|
61
|
+
const row = toThreadRowData(
|
|
62
|
+
threadResponse({ hasStars: true, star: "none" }),
|
|
63
|
+
);
|
|
64
|
+
assert.strictEqual(row.starred, true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a message with a star colour is starred", () => {
|
|
68
|
+
const row = toThreadRowData(
|
|
69
|
+
threadResponse({ hasStars: true, star: "yellow" }),
|
|
70
|
+
);
|
|
71
|
+
assert.strictEqual(row.starred, true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a message without hasStars is not starred", () => {
|
|
75
|
+
const row = toThreadRowData(
|
|
76
|
+
threadResponse({ hasStars: false, star: "yellow" }),
|
|
77
|
+
);
|
|
78
|
+
assert.strictEqual(row.starred, false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
28
82
|
describe("groupBriefSections", () => {
|
|
29
83
|
test("returns empty array when no rows", () => {
|
|
30
84
|
const sections = groupBriefSections([]);
|
package/src/lib/brief.ts
CHANGED
|
@@ -57,8 +57,7 @@ export function toThreadRowData(
|
|
|
57
57
|
sentDate: thread.sentDate,
|
|
58
58
|
isRead: thread.isRead,
|
|
59
59
|
hasAttachment: thread.hasAttachment,
|
|
60
|
-
starred:
|
|
61
|
-
thread.star != null && thread.star !== "none" && thread.hasStars === true,
|
|
60
|
+
starred: thread.hasStars === true,
|
|
62
61
|
trust: thread.senderTrust as SenderTrustLevel,
|
|
63
62
|
category: toDisplayCategory(thread.category),
|
|
64
63
|
suspicious,
|
package/src/lib/keymap.ts
CHANGED
|
@@ -130,7 +130,7 @@ export const KEY_HINT_GROUPS: KeyHintGroup[] = [
|
|
|
130
130
|
{ action: "goBrief", keys: ["g", "b"], description: "Daily brief" },
|
|
131
131
|
{ action: "goInbox", keys: ["g", "i"], description: "Inbox" },
|
|
132
132
|
{ action: "goSent", keys: ["g", "s"], description: "Sent" },
|
|
133
|
-
{ action: "goFlagged", keys: ["g", "f"], description: "
|
|
133
|
+
{ action: "goFlagged", keys: ["g", "f"], description: "Starred" },
|
|
134
134
|
{ action: "goSettings", keys: ["g", ","], description: "Settings" },
|
|
135
135
|
],
|
|
136
136
|
},
|
|
@@ -15,7 +15,7 @@ import { ErrorState } from "@/components/ui/ErrorState";
|
|
|
15
15
|
const FlaggedError = ({ error, reset }: ErrorComponentProps) => (
|
|
16
16
|
<div className="flex h-full items-center justify-center bg-canvas p-4">
|
|
17
17
|
<ErrorState
|
|
18
|
-
title="Couldn't load your
|
|
18
|
+
title="Couldn't load your starred mail"
|
|
19
19
|
error={error}
|
|
20
20
|
onRetry={reset}
|
|
21
21
|
/>
|
package/vite.base.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import tailwindcss from "@tailwindcss/vite";
|
|
4
4
|
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
|
5
5
|
import react from "@vitejs/plugin-react";
|
|
6
|
-
import type { AliasOptions, PluginOption } from "vite";
|
|
6
|
+
import type { AliasOptions, BuildOptions, PluginOption } from "vite";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The one place the web-client build toolchain is described. Both the dev
|
|
@@ -36,6 +36,20 @@ export const webClientDefine = (): Record<string, string> => ({
|
|
|
36
36
|
__APP_BUILD_TIME__: JSON.stringify(new Date().toISOString()),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Rollup keeps up to `maxParallelFileOps` module reads in flight, and each one
|
|
41
|
+
* holds a file descriptor. Its default of 1000 is above the 1024 `RLIMIT_NOFILE`
|
|
42
|
+
* soft limit that container runtimes hand a build step, and the icon barrels this
|
|
43
|
+
* app imports (lucide-react re-exports ~1500 single-icon modules) give rollup
|
|
44
|
+
* enough sibling modules to saturate the ceiling, so the default reliably fails
|
|
45
|
+
* with EMFILE inside an image build while passing on a developer machine.
|
|
46
|
+
* Reading 64 files at a time keeps the build IO-bound without depending on the
|
|
47
|
+
* descriptor budget of whatever runs it.
|
|
48
|
+
*/
|
|
49
|
+
export const webClientBuild = (): BuildOptions => ({
|
|
50
|
+
rollupOptions: { maxParallelFileOps: 64 },
|
|
51
|
+
});
|
|
52
|
+
|
|
39
53
|
export const webClientPlugins = (routes: RouterPaths = {}): PluginOption[] => [
|
|
40
54
|
tanstackRouter({ target: "react", autoCodeSplitting: true, ...routes }),
|
|
41
55
|
react(),
|