@marimo-team/islands 0.24.3-dev22 → 0.24.3-dev24

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.
@@ -32442,7 +32442,7 @@ function Iq() {
32442
32442
  return a.warn("Failed to get version from mount config"), null;
32443
32443
  }
32444
32444
  }
32445
- var Lq = Iq() || "0.24.3-dev22", Rq = V(Lq), zq = V(!0);
32445
+ var Lq = Iq() || "0.24.3-dev24", Rq = V(Lq), zq = V(!0);
32446
32446
  V(null);
32447
32447
  //#endregion
32448
32448
  //#region src/core/wasm/state.ts
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@ import { n as x, r as S, t as C } from "./copy-C0N4m1lm.js";
6
6
  import { $n as w, An as T, B as E, Bn as D, Bt as O, C as k, Ct as A, Dn as j, Dt as M, En as N, F as P, Fn as F, G as I, I as L, It as R, J as ee, Jt as z, K as te, Kn as B, L as ne, Lt as re, Mt as ie, Nn as ae, Nt as oe, On as se, Ot as ce, P as le, Pn as ue, Pt as de, Q as fe, Qn as pe, Qt as me, R as he, Rn as ge, Sn as _e, St as ve, U as ye, Un as be, Vn as xe, Vt as Se, W as Ce, Wn as we, Wt as Te, X as Ee, Xn as De, Y as Oe, Yn as ke, Yt as Ae, Z as je, Zn as Me, Zt as Ne, _ as Pe, _n as Fe, a as Ie, an as Le, ar as Re, at as ze, b as Be, bn as Ve, bt as He, cn as Ue, ct as We, dn as Ge, dt as Ke, et as qe, fn as Je, ft as Ye, g as Xe, i as Ze, ir as Qe, j as $e, jt as et, k as tt, kn as nt, kt as rt, ln as it, n as at, nr as ot, o as st, on as ct, ot as lt, q as ut, qt as dt, sn as ft, st as pt, tn as mt, tr as ht, un as gt, ut as _t, v as vt, wn as yt, wt as bt, xn as xt, xt as St, y as Ct, z as wt, zn as Tt } from "./html-to-image-CSU-_5BI.js";
7
7
  import { o as Et, s as Dt, u as Ot } from "./dist-6ChWbo8z.js";
8
8
  import { D as kt, E as At, O as jt, S as Mt, T as Nt, _ as Pt, a as Ft, c as It, d as Lt, f as Rt, g as zt, h as Bt, i as Vt, l as Ht, m as Ut, n as Wt, o as Gt, p as Kt, r as qt, s as Jt, t as Yt, u as Xt, v as Zt, w as Qt, x as $t, y as en } from "./spec-g740fOWu.js";
9
- import { $ as tn, $t as nn, A as rn, At as an, B as on, Bt as sn, C as cn, Cn as ln, Ct as un, D as dn, Dn as fn, Dt as pn, E as mn, En as hn, Et as gn, F as _n, Ft as vn, G as yn, Gt as bn, H as xn, Ht as Sn, I as Cn, It as wn, J as Tn, Jt as En, K as Dn, Kt as On, L as kn, Lt as An, M as jn, N as Mn, Nt as Nn, O as Pn, Ot as Fn, P as In, Pt as Ln, Q as Rn, Qt as zn, R as Bn, Rt as Vn, S as Hn, Sn as Un, St as Wn, T as Gn, Tn as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Z as ir, Zt as ar, _ as or, _n as sr, _t as cr, a as lr, an as ur, at as dr, b as fr, bn as pr, bt as mr, cn as hr, dn as gr, dt as _r, en as vr, et as yr, fn as br, ft as xr, g as Sr, gn as Cr, gt as wr, hn as Tr, ht as Er, i as Dr, in as Or, it as kr, j as Ar, k as jr, kt as Mr, l as Nr, ln as Pr, m as Fr, mn as Ir, mt as Lr, n as Rr, nn as zr, nt as Br, on as Vr, ot as Hr, pn as Ur, pt as Wr, q as Gr, qt as Kr, rn as qr, rt as Jr, s as Yr, sn as Xr, st as Zr, t as Qr, tn as $r, tt as ei, un as ti, v as ni, vt as ri, w as ii, wn as ai, wt as oi, x as si, xn as ci, xt as li, y as ui, yn as di, yt as fi, z as pi, zt as mi } from "./common-DIyUaEO6.js";
9
+ import { $ as tn, $t as nn, A as rn, At as an, B as on, Bt as sn, C as cn, Cn as ln, Ct as un, D as dn, Dn as fn, Dt as pn, E as mn, En as hn, Et as gn, F as _n, Ft as vn, G as yn, Gt as bn, H as xn, Ht as Sn, I as Cn, It as wn, J as Tn, Jt as En, K as Dn, Kt as On, L as kn, Lt as An, M as jn, N as Mn, Nt as Nn, O as Pn, Ot as Fn, P as In, Pt as Ln, Q as Rn, Qt as zn, R as Bn, Rt as Vn, S as Hn, Sn as Un, St as Wn, T as Gn, Tn as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Z as ir, Zt as ar, _ as or, _n as sr, _t as cr, a as lr, an as ur, at as dr, b as fr, bn as pr, bt as mr, cn as hr, dn as gr, dt as _r, en as vr, et as yr, fn as br, ft as xr, g as Sr, gn as Cr, gt as wr, hn as Tr, ht as Er, i as Dr, in as Or, it as kr, j as Ar, k as jr, kt as Mr, l as Nr, ln as Pr, m as Fr, mn as Ir, mt as Lr, n as Rr, nn as zr, nt as Br, on as Vr, ot as Hr, pn as Ur, pt as Wr, q as Gr, qt as Kr, rn as qr, rt as Jr, s as Yr, sn as Xr, st as Zr, t as Qr, tn as $r, tt as ei, un as ti, v as ni, vt as ri, w as ii, wn as ai, wt as oi, x as si, xn as ci, xt as li, y as ui, yn as di, yt as fi, z as pi, zt as mi } from "./common-BzvhwpFa.js";
10
10
  import { c as hi, i as gi, n as _i, o as vi, t as yi } from "./useLifecycle-BGRBkTFT.js";
11
11
  import { t as bi } from "./check-mcQyZmFG.js";
12
12
  import { A as xi, C as Si, D as Ci, E as wi, F as Ti, I as Ei, M as Di, N as Oi, P as ki, R as Ai, S as ji, a as Mi, b as Ni, c as Pi, i as Fi, j as Ii, l as Li, n as Ri, o as zi, r as Bi, s as Vi, t as Hi, w as Ui, x as Wi } from "./select-UMOSCa3W.js";
@@ -29942,7 +29942,7 @@ function xR(e) {
29942
29942
  }
29943
29943
  //#endregion
29944
29944
  //#region src/components/editor/renderers/slides-layout/slides-layout.tsx
29945
- var SR = q.lazy(() => import("./reveal-component-GEGc56E6.js")), CR = (e) => {
29945
+ var SR = q.lazy(() => import("./reveal-component-D5xyD8xS.js")), CR = (e) => {
29946
29946
  let t = (0, Y.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = fs(rt), s = a === "read" || o, c = fs(Oe) > 1, [l, u] = (0, q.useState)(null), d;
29947
29947
  t[0] !== i || t[1] !== n ? (d = yP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
29948
29948
  let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
@@ -4,7 +4,7 @@ import { t as o } from "./react-ofJbxM4x.js";
4
4
  import { t as s } from "./compiler-runtime-Cj8ZWiNS.js";
5
5
  import { kt as c, st as l } from "./html-to-image-CSU-_5BI.js";
6
6
  import { u } from "./dist-6ChWbo8z.js";
7
- import { _n as d, c as f, ct as p, g as m, h, l as g, lt as _, n as v, o as y, r as b, t as x, u as S, ut as C, vn as w, xn as T } from "./common-DIyUaEO6.js";
7
+ import { _n as d, c as f, ct as p, g as m, h, l as g, lt as _, n as v, o as y, r as b, t as x, u as S, ut as C, vn as w, xn as T } from "./common-BzvhwpFa.js";
8
8
  import { R as E } from "./select-UMOSCa3W.js";
9
9
  import { Y as D } from "./dropdown-menu-C73aTomr.js";
10
10
  import { t as O } from "./jsx-runtime-DgDPJV9S.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.24.3-dev22",
3
+ "version": "0.24.3-dev24",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,270 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ act,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor,
9
+ } from "@testing-library/react";
10
+ import { Provider } from "jotai";
11
+ import type { ComponentProps } from "react";
12
+ import { JsonRpcError } from "use-acp";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
+ import { MockRequestClient } from "@/__mocks__/requests";
15
+ import type { PromptInput } from "@/components/editor/ai/add-cell-with-ai";
16
+ import { TooltipProvider } from "@/components/ui/tooltip";
17
+ import { toast } from "@/components/ui/use-toast";
18
+ import { pendingAiPromptAtom } from "@/core/ai/state";
19
+ import { requestClientAtom } from "@/core/network/requests";
20
+ import { cwdAtom, filenameAtom } from "@/core/saving/file-state";
21
+ import { store } from "@/core/state/jotai";
22
+ import AgentPanel from "../agent-panel";
23
+ import { parseContextFromPrompt } from "../context-utils";
24
+ import {
25
+ addSession,
26
+ agentSessionStateAtom,
27
+ selectedTabAtom,
28
+ updateSessionExternalAgentSessionId,
29
+ } from "../state";
30
+ import type { ExternalAgentSessionId } from "../types";
31
+
32
+ const { agent, client } = vi.hoisted(() => {
33
+ const agent = {
34
+ initialize: vi.fn().mockResolvedValue({}),
35
+ prompt: vi.fn().mockResolvedValue({ stopReason: "end_turn" }),
36
+ cancel: vi.fn().mockResolvedValue({}),
37
+ };
38
+ return {
39
+ agent,
40
+ client: {
41
+ agent,
42
+ activeSessionId: "session-1",
43
+ connectionState: { status: "connected" },
44
+ notifications: [],
45
+ connect: vi.fn().mockResolvedValue(undefined),
46
+ disconnect: vi.fn(),
47
+ setActiveSessionId: vi.fn(),
48
+ },
49
+ };
50
+ });
51
+
52
+ vi.mock("use-acp", async (importOriginal) => ({
53
+ ...(await importOriginal<typeof import("use-acp")>()),
54
+ useAcpClient: () => client,
55
+ }));
56
+
57
+ vi.mock("@/components/editor/ai/add-cell-with-ai", () => ({
58
+ PromptInput: ({
59
+ value,
60
+ onChange,
61
+ onAddFiles,
62
+ onSubmit,
63
+ }: ComponentProps<typeof PromptInput>) => (
64
+ <>
65
+ <textarea
66
+ aria-label="Prompt"
67
+ value={value}
68
+ onChange={(event) => onChange(event.target.value)}
69
+ onKeyDown={(event) => {
70
+ if (event.key === "Enter") {
71
+ onSubmit(undefined, value);
72
+ }
73
+ }}
74
+ />
75
+ <input
76
+ type="file"
77
+ aria-label="Attachment"
78
+ onChange={(event) => onAddFiles?.([...(event.target.files ?? [])])}
79
+ />
80
+ </>
81
+ ),
82
+ }));
83
+ vi.mock("../session-tabs", () => ({ SessionTabs: () => null }));
84
+ vi.mock("../blocks", () => ({ ReadyToChatBlock: () => null }));
85
+ vi.mock("../context-utils", async (importOriginal) => ({
86
+ ...(await importOriginal<typeof import("../context-utils")>()),
87
+ parseContextFromPrompt: vi
88
+ .fn()
89
+ .mockResolvedValue({ contextBlocks: [], attachmentBlocks: [] }),
90
+ }));
91
+ vi.mock("@/components/ui/use-toast", () => ({ toast: vi.fn() }));
92
+
93
+ function renderPanel() {
94
+ return render(
95
+ <Provider store={store}>
96
+ <TooltipProvider>
97
+ <AgentPanel />
98
+ </TooltipProvider>
99
+ </Provider>,
100
+ );
101
+ }
102
+
103
+ function submitPrompt(prompt = "Explain this notebook") {
104
+ fireEvent.change(screen.getByRole("textbox", { name: "Prompt" }), {
105
+ target: { value: prompt },
106
+ });
107
+ fireEvent.keyDown(screen.getByRole("textbox", { name: "Prompt" }), {
108
+ key: "Enter",
109
+ });
110
+ }
111
+
112
+ describe("AgentPanel prompt submission", () => {
113
+ beforeEach(() => {
114
+ agent.prompt.mockReset().mockResolvedValue({ stopReason: "end_turn" });
115
+ vi.mocked(parseContextFromPrompt).mockReset().mockResolvedValue({
116
+ contextBlocks: [],
117
+ attachmentBlocks: [],
118
+ });
119
+ store.set(requestClientAtom, MockRequestClient.create());
120
+ store.set(filenameAtom, "notebook.py");
121
+ store.set(cwdAtom, "/notebooks");
122
+ store.set(pendingAiPromptAtom, null);
123
+ store.set(
124
+ agentSessionStateAtom,
125
+ updateSessionExternalAgentSessionId(
126
+ addSession({ sessions: [], activeTabId: null }, { agentId: "claude" }),
127
+ "session-1" as ExternalAgentSessionId,
128
+ ),
129
+ );
130
+ });
131
+
132
+ afterEach(() => {
133
+ store.set(pendingAiPromptAtom, null);
134
+ store.set(agentSessionStateAtom, { sessions: [], activeTabId: null });
135
+ store.set(filenameAtom, null);
136
+ store.set(cwdAtom, null);
137
+ });
138
+
139
+ it("preserves the draft, attachment and title when the notebook is unnamed", async () => {
140
+ store.set(filenameAtom, null);
141
+ renderPanel();
142
+ const title = store.get(selectedTabAtom)?.title;
143
+ fireEvent.change(screen.getByLabelText("Attachment"), {
144
+ target: {
145
+ files: [new File(["notes"], "notes.txt", { type: "text/plain" })],
146
+ },
147
+ });
148
+ submitPrompt();
149
+
150
+ expect(toast).toHaveBeenCalledWith({
151
+ title: "Notebook must be named",
152
+ description: "Please name the notebook to use the agent",
153
+ variant: "danger",
154
+ });
155
+ expect(agent.prompt).not.toHaveBeenCalled();
156
+ expect(screen.getByRole("textbox", { name: "Prompt" })).toHaveValue(
157
+ "Explain this notebook",
158
+ );
159
+ expect(screen.getByText("notes.txt")).toBeInTheDocument();
160
+ expect(store.get(selectedTabAtom)?.title).toBe(title);
161
+ expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
162
+
163
+ act(() => store.set(filenameAtom, "notebook.py"));
164
+ fireEvent.keyDown(screen.getByRole("textbox", { name: "Prompt" }), {
165
+ key: "Enter",
166
+ });
167
+ await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
168
+ expect(agent.prompt).toHaveBeenCalledWith({
169
+ sessionId: "session-1",
170
+ prompt: expect.arrayContaining([
171
+ { type: "text", text: "Explain this notebook" },
172
+ expect.objectContaining({ type: "resource_link", name: "notes.txt" }),
173
+ ]),
174
+ });
175
+ await waitFor(() =>
176
+ expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument(),
177
+ );
178
+ });
179
+
180
+ it.each([new Error("Agent unavailable"), "Agent unavailable"])(
181
+ "shows a rejected prompt and allows another submission after dismissal (%s)",
182
+ async (error) => {
183
+ agent.prompt.mockRejectedValueOnce(error);
184
+ renderPanel();
185
+ submitPrompt();
186
+
187
+ expect(await screen.findByText(/Agent unavailable/)).toBeInTheDocument();
188
+ fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
189
+ expect(screen.queryByText(/Agent unavailable/)).not.toBeInTheDocument();
190
+ submitPrompt("Try again");
191
+ await waitFor(() => expect(agent.prompt).toHaveBeenCalledTimes(2));
192
+ await waitFor(() =>
193
+ expect(
194
+ screen.queryByText("Agent is working..."),
195
+ ).not.toBeInTheDocument(),
196
+ );
197
+ },
198
+ );
199
+
200
+ it("preserves the existing authentication error action and detail", async () => {
201
+ agent.prompt.mockRejectedValueOnce(
202
+ new JsonRpcError({
203
+ code: -32_000,
204
+ message: "Authentication required",
205
+ data: { message: "Sign in to the agent" },
206
+ }),
207
+ );
208
+ renderPanel();
209
+ submitPrompt();
210
+
211
+ expect(await screen.findByText("Sign in to the agent")).toBeInTheDocument();
212
+ expect(
213
+ screen.getByRole("button", { name: "Restart session" }),
214
+ ).toBeInTheDocument();
215
+ expect(
216
+ screen.queryByRole("button", { name: "Dismiss" }),
217
+ ).not.toBeInTheDocument();
218
+ });
219
+
220
+ it("clears busy state and reports failures while preparing prompt context", async () => {
221
+ vi.mocked(parseContextFromPrompt).mockRejectedValueOnce(
222
+ new Error("Context unavailable"),
223
+ );
224
+ renderPanel();
225
+ submitPrompt();
226
+
227
+ expect(await screen.findByText("Context unavailable")).toBeInTheDocument();
228
+ expect(agent.prompt).not.toHaveBeenCalled();
229
+ fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
230
+ expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
231
+ submitPrompt();
232
+ await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
233
+ });
234
+
235
+ it("reports failures from a queued prompt through the same handler", async () => {
236
+ agent.prompt.mockRejectedValueOnce(new Error("Queued prompt failed"));
237
+ renderPanel();
238
+ act(() =>
239
+ store.set(pendingAiPromptAtom, {
240
+ prompt: "Help with this cell",
241
+ submit: true,
242
+ }),
243
+ );
244
+
245
+ expect(await screen.findByText("Queued prompt failed")).toBeInTheDocument();
246
+ expect(store.get(pendingAiPromptAtom)).toBeNull();
247
+ expect(agent.prompt).toHaveBeenCalledWith({
248
+ sessionId: "session-1",
249
+ prompt: expect.arrayContaining([
250
+ { type: "text", text: "Help with this cell" },
251
+ ]),
252
+ });
253
+ });
254
+
255
+ it("clears busy state when a cancelled prompt settles", async () => {
256
+ const response = Promise.withResolvers<{ stopReason: string }>();
257
+ agent.prompt.mockReturnValueOnce(response.promise);
258
+ renderPanel();
259
+ submitPrompt();
260
+ await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
261
+ expect(screen.getByText("Agent is working...")).toBeInTheDocument();
262
+ fireEvent.click(screen.getByRole("button", { name: "Stop" }));
263
+ await act(async () => response.resolve({ stopReason: "cancelled" }));
264
+ expect(agent.cancel).toHaveBeenCalledWith({ sessionId: "session-1" });
265
+ expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
266
+ expect(
267
+ screen.queryByRole("button", { name: "Dismiss" }),
268
+ ).not.toBeInTheDocument();
269
+ });
270
+ });
@@ -902,6 +902,18 @@ const AgentPanel: React.FC = () => {
902
902
  return;
903
903
  }
904
904
 
905
+ let absoluteFilename: string;
906
+ try {
907
+ absoluteFilename = getAbsoluteFilename();
908
+ } catch {
909
+ toast({
910
+ title: "Notebook must be named",
911
+ description: "Please name the notebook to use the agent",
912
+ variant: "danger",
913
+ });
914
+ return;
915
+ }
916
+
905
917
  logger.debug("Submitting prompt to agent", {
906
918
  sessionId: activeSessionId,
907
919
  });
@@ -914,62 +926,51 @@ const AgentPanel: React.FC = () => {
914
926
  setSessionState((prev) => updateSessionTitle(prev, prompt));
915
927
  }
916
928
 
917
- let absoluteFilename: string;
918
929
  try {
919
- absoluteFilename = getAbsoluteFilename();
920
- } catch {
921
- toast({
922
- title: "Notebook must be named",
923
- description: "Please name the notebook to use the agent",
924
- variant: "danger",
925
- });
926
- return;
927
- }
928
-
929
- const promptBlocks: ContentBlock[] = [{ type: "text", text: prompt }];
930
+ const promptBlocks: ContentBlock[] = [{ type: "text", text: prompt }];
930
931
 
931
- // Parse context from the prompt
932
- const { contextBlocks, attachmentBlocks } =
933
- await parseContextFromPrompt(prompt);
934
- promptBlocks.push(...contextBlocks, ...attachmentBlocks);
932
+ // Parse context from the prompt
933
+ const { contextBlocks, attachmentBlocks } =
934
+ await parseContextFromPrompt(prompt);
935
+ promptBlocks.push(...contextBlocks, ...attachmentBlocks);
935
936
 
936
- // Add manually uploaded files as resource links
937
- if (files && files.length > 0) {
938
- const fileResourceLinks = await convertFilesToResourceLinks(files);
939
- promptBlocks.push(...fileResourceLinks);
940
- }
937
+ // Add manually uploaded files as resource links
938
+ if (files && files.length > 0) {
939
+ const fileResourceLinks = await convertFilesToResourceLinks(files);
940
+ promptBlocks.push(...fileResourceLinks);
941
+ }
941
942
 
942
- const hasGivenRules = notifications.some(
943
- (notification) =>
944
- notification.type === "session_notification" &&
945
- notification.data.update.sessionUpdate === "user_message_chunk",
946
- );
947
- if (!hasGivenRules) {
948
- promptBlocks.push(
949
- {
950
- type: "resource_link",
951
- uri: absoluteFilename,
952
- mimeType: "text/x-python",
953
- name: absoluteFilename,
954
- },
955
- {
956
- type: "resource",
957
- resource: {
958
- uri: "marimo_rules.md",
959
- mimeType: "text/plain",
960
- text: getAgentPrompt(absoluteFilename),
961
- },
962
- },
943
+ const hasGivenRules = notifications.some(
944
+ (notification) =>
945
+ notification.type === "session_notification" &&
946
+ notification.data.update.sessionUpdate === "user_message_chunk",
963
947
  );
964
- }
948
+ if (!hasGivenRules) {
949
+ promptBlocks.push(
950
+ {
951
+ type: "resource_link",
952
+ uri: absoluteFilename,
953
+ mimeType: "text/x-python",
954
+ name: absoluteFilename,
955
+ },
956
+ {
957
+ type: "resource",
958
+ resource: {
959
+ uri: "marimo_rules.md",
960
+ mimeType: "text/plain",
961
+ text: getAgentPrompt(absoluteFilename),
962
+ },
963
+ },
964
+ );
965
+ }
965
966
 
966
- try {
967
967
  await agent.prompt({
968
968
  sessionId: activeSessionId,
969
969
  prompt: promptBlocks,
970
970
  });
971
971
  } catch (error) {
972
972
  logger.error("Failed to send prompt", { error });
973
+ setError(error instanceof Error ? error : String(error));
973
974
  } finally {
974
975
  setIsLoading(false);
975
976
  }