@nextclaw/ui 0.15.13 → 0.15.15

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 (87) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/assets/{api-DGG69ZcJ.js → api-D5UCLVE_.js} +3 -3
  3. package/dist/assets/app-presenter-provider-DcpcRgLn.js +106 -0
  4. package/dist/assets/{appearance-settings-page-BZSd1SrH.js → appearance-settings-page-yJR6Kt9c.js} +1 -1
  5. package/dist/assets/{book-open-BhaS5BJv.js → book-open-WsEnuW9P.js} +1 -1
  6. package/dist/assets/{card-Bcr5YM8F.js → card-xVrS1b1T.js} +1 -1
  7. package/dist/assets/{channels-list-page-C_x83ZCT.js → channels-list-page-CAWuTHSM.js} +1 -1
  8. package/dist/assets/{chat-page-D2xfR6TK.js → chat-page-CXYXMIQQ.js} +1 -1
  9. package/dist/assets/{config-split-page-RXAoLeNv.js → config-split-page-xj0UtMow.js} +1 -1
  10. package/dist/assets/{confirm-dialog-o_VPKvVD.js → confirm-dialog-e2YCUzt1.js} +1 -1
  11. package/dist/assets/{desktop-update-config-DEOMOsDN.js → desktop-update-config-D5NIaimo.js} +1 -1
  12. package/dist/assets/{dist-C87P4QWA.js → dist-Ca-jiFrK.js} +1 -1
  13. package/dist/assets/doc-browser-B2tNEKrJ.js +1 -0
  14. package/dist/assets/{doc-browser-DQ75iUkA.js → doc-browser-CZX6I6CU.js} +1 -1
  15. package/dist/assets/{doc-browser-context-tE0--wSa.js → doc-browser-context-CGz0LULR.js} +1 -1
  16. package/dist/assets/{eye-DL9QkrKg.js → eye-B30J471A.js} +1 -1
  17. package/dist/assets/{form-actions-J0tIaUdK.js → form-actions-CnSzu3WD.js} +1 -1
  18. package/dist/assets/{index-B9QEjnoY.js → index-6Esxu-Pp.js} +2 -2
  19. package/dist/assets/index-BzSgA8tR.css +1 -0
  20. package/dist/assets/loader-circle-DjwSRQHW.js +1 -0
  21. package/dist/assets/{logo-badge-BYTbZbOF.js → logo-badge-CISsltAo.js} +1 -1
  22. package/dist/assets/mcp-marketplace-page-BZa7p7Qf.js +1 -0
  23. package/dist/assets/{mcp-marketplace-page-COWlzeP1.js → mcp-marketplace-page-CPcPzMUQ.js} +1 -1
  24. package/dist/assets/{model-config-page-oNqnMXNY.js → model-config-page-D6ex3_NM.js} +1 -1
  25. package/dist/assets/{navigation-link-Dk6VdDkc.js → navigation-link-qPPUiLt0.js} +1 -1
  26. package/dist/assets/plus-C45awBb1.js +1 -0
  27. package/dist/assets/{popover-Txwrum65.js → popover-DHx55dbc.js} +1 -1
  28. package/dist/assets/{provider-scoped-model-input-B7rQM4b6.js → provider-scoped-model-input-1TJxAhx6.js} +1 -1
  29. package/dist/assets/{providers-config-page-CP-OnvLK.js → providers-config-page-BwHtHIPe.js} +1 -1
  30. package/dist/assets/{react-B0LbRnAE.js → react-DerwD2EA.js} +1 -1
  31. package/dist/assets/{refresh-cw-C84mMku5.js → refresh-cw-C7bynrWX.js} +1 -1
  32. package/dist/assets/remote-CPpTKDgX.js +1 -0
  33. package/dist/assets/rotate-cw-Bhx0XkQP.js +1 -0
  34. package/dist/assets/{runtime-config-page-u_fuHxc8.js → runtime-config-page-CMLVC5UV.js} +1 -1
  35. package/dist/assets/{save-R_qyrHpa.js → save-CsRsFtHS.js} +1 -1
  36. package/dist/assets/{search-UKxiouNi.js → search-D0HtJIL-.js} +1 -1
  37. package/dist/assets/{search-config-page-DzkpSGFH.js → search-config-page-n1GElgij.js} +1 -1
  38. package/dist/assets/{secrets-config-page-DhoLYm7Z.js → secrets-config-page-DbUc3Ggi.js} +1 -1
  39. package/dist/assets/security-config-n2WHhHXV.js +1 -0
  40. package/dist/assets/{select-CYHT8P5S.js → select-C1zfp9zM.js} +1 -1
  41. package/dist/assets/{settings-2-DGh4bGSi.js → settings-2-Ba3cLSN2.js} +1 -1
  42. package/dist/assets/skeleton-DL5npsVL.js +1 -0
  43. package/dist/assets/{tabs-BCihm1zK.js → tabs-DmKgMKzb.js} +1 -1
  44. package/dist/assets/{tag-chip-De0iz4o2.js → tag-chip-2vwlVUwM.js} +1 -1
  45. package/dist/assets/{trash-2-Bo3DWiju.js → trash-2-CUpWjNHu.js} +1 -1
  46. package/dist/assets/{use-config-DCzwgChy.js → use-config-BvxDWV-M.js} +1 -1
  47. package/dist/assets/{use-confirm-dialog-WZ94CehU.js → use-confirm-dialog-CeUEE4p5.js} +1 -1
  48. package/dist/assets/{use-viewport-layout-CZ-3pMne.js → use-viewport-layout-DaKJ-kap.js} +1 -1
  49. package/dist/assets/x-DGdYT7rH.js +1 -0
  50. package/dist/index.html +28 -28
  51. package/package.json +9 -9
  52. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -1
  53. package/src/features/chat/components/conversation/chat-conversation-content.tsx +2 -2
  54. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +11 -0
  55. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +4 -0
  56. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +3 -0
  57. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +64 -72
  58. package/src/features/chat/features/conversation/hooks/__tests__/use-session-run-queue.test.tsx +85 -0
  59. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +108 -257
  60. package/src/features/chat/features/conversation/hooks/use-session-run-queue.ts +52 -0
  61. package/src/features/chat/features/conversation/utils/__tests__/session-queued-input.utils.test.ts +63 -0
  62. package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +134 -0
  63. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +5 -5
  64. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +113 -32
  65. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +3 -2
  66. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +118 -12
  67. package/src/features/chat/features/message/components/__tests__/chat-message-list-skill-preview.container.test.tsx +170 -0
  68. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +18 -9
  69. package/src/features/chat/features/message/components/chat-message-list.container.tsx +36 -23
  70. package/src/features/chat/features/message/hooks/__tests__/use-chat-message-virtualizer.test.tsx +90 -0
  71. package/src/features/chat/features/message/hooks/use-chat-message-virtualizer.ts +3 -5
  72. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime-queue.test.tsx +51 -0
  73. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +9 -3
  74. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +42 -8
  75. package/src/shared/lib/i18n/locales/en-US/chat.json +1 -0
  76. package/src/shared/lib/i18n/locales/zh-CN/chat.json +1 -0
  77. package/dist/assets/app-presenter-provider-B1RgR5ug.js +0 -106
  78. package/dist/assets/doc-browser-BuGh646I.js +0 -1
  79. package/dist/assets/index-B2YF-7Yw.css +0 -1
  80. package/dist/assets/loader-circle-CAnHHSrY.js +0 -1
  81. package/dist/assets/mcp-marketplace-page-CaUFv37E.js +0 -1
  82. package/dist/assets/plus-DjdTWPjQ.js +0 -1
  83. package/dist/assets/remote-2gZkJSX4.js +0 -1
  84. package/dist/assets/rotate-cw-DQJhDnjg.js +0 -1
  85. package/dist/assets/security-config-yraYoy9s.js +0 -1
  86. package/dist/assets/skeleton-BQuylYFO.js +0 -1
  87. package/dist/assets/x-NZxXyqW1.js +0 -1
@@ -181,6 +181,17 @@ vi.mock(
181
181
  }),
182
182
  );
183
183
 
184
+ vi.mock(
185
+ "@/features/chat/features/conversation/hooks/use-session-run-queue",
186
+ () => ({
187
+ useSessionRunQueue: () => ({
188
+ inputs: [],
189
+ isLoading: false,
190
+ removeQueuedInput: vi.fn(async () => null),
191
+ }),
192
+ }),
193
+ );
194
+
184
195
  vi.mock(
185
196
  "@/features/chat/features/runtime/utils/ncp-chat-runtime-availability.utils",
186
197
  () => ({
@@ -387,6 +387,10 @@ function AttachmentSubmitHarness({ send }: { readonly send: AttachmentSubmitAgen
387
387
  inputSnapshot,
388
388
  inputQuery,
389
389
  isRuntimeBlocked: false,
390
+ runQueue: {
391
+ inputs: [],
392
+ removeQueuedInput: async () => null,
393
+ },
390
394
  selectedAgentId: 'main',
391
395
  sessionKey: 'session-attachment',
392
396
  resetComposer: inputActions.resetComposer,
@@ -25,6 +25,7 @@ import {
25
25
  } from "@/features/chat/features/conversation/hooks/use-session-conversation-controller";
26
26
  import { useSessionConversationInputQuery } from "@/features/chat/features/conversation/hooks/use-session-conversation-input-query";
27
27
  import { useSessionConversationInputState } from "@/features/chat/features/conversation/hooks/use-session-conversation-input-state";
28
+ import { useSessionRunQueue } from "@/features/chat/features/conversation/hooks/use-session-run-queue";
28
29
  import {
29
30
  SessionConversationInput,
30
31
  type SessionConversationInputController,
@@ -192,6 +193,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
192
193
  (state) => state.snapshot.selectedAgentId,
193
194
  );
194
195
  const agent = useNcpSessionConversation(sessionKey ?? undefined);
196
+ const runQueue = useSessionRunQueue(sessionKey);
195
197
  const { inputActions, inputSnapshot } = useSessionConversationInputState(initialPrompt);
196
198
  const inputQuery = useSessionConversationInputQuery({
197
199
  sessionKey,
@@ -257,6 +259,7 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
257
259
  inputQuery,
258
260
  isRuntimeBlocked,
259
261
  materializationContext,
262
+ runQueue,
260
263
  selectedAgentId,
261
264
  sessionKey,
262
265
  onSessionMaterialized,
@@ -1,11 +1,11 @@
1
1
  import { act, renderHook, waitFor } from '@testing-library/react';
2
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import type { UiNcpSessionQueuedInputView } from '@nextclaw/client-sdk';
3
4
  import type { NcpAgentSendEnvelope, NcpRunHandle } from '@nextclaw/ncp';
4
5
 
5
6
  import { useSessionConversationController } from '@/features/chat/features/conversation/hooks/use-session-conversation-controller';
6
7
 
7
- type TestAgentSendEnvelope = NcpAgentSendEnvelope;
8
- type TestAgentSend = (envelope: TestAgentSendEnvelope) => Promise<NcpRunHandle | null>;
8
+ type TestAgentSend = (envelope: NcpAgentSendEnvelope) => Promise<NcpRunHandle | null>;
9
9
 
10
10
  function createTextNode(text: string) {
11
11
  return {
@@ -26,34 +26,49 @@ function createRunHandle(overrides: Partial<NcpRunHandle> = {}): NcpRunHandle {
26
26
  };
27
27
  }
28
28
 
29
- function createSendMock() {
30
- return vi.fn<TestAgentSend>(async () => createRunHandle());
31
- }
32
-
33
- function createAgent(params: {
34
- isRunning: boolean;
35
- send: ReturnType<typeof createSendMock>;
36
- }) {
29
+ function createQueuedInput(): UiNcpSessionQueuedInputView {
37
30
  return {
38
- abort: vi.fn(),
39
- isHydrating: false,
40
- isRunning: params.isRunning,
41
- isSending: false,
42
- send: params.send,
43
- snapshot: {
44
- activeRun: params.isRunning ? { sessionId: 'session-1' } : null,
31
+ id: 'queued-input-1',
32
+ sessionId: 'session-1',
33
+ enqueuedAt: '2026-07-05T10:00:00.000Z',
34
+ metadata: {},
35
+ message: {
36
+ id: 'user-message-queued',
37
+ sessionId: 'session-1',
38
+ role: 'user',
39
+ status: 'final',
40
+ timestamp: '2026-07-05T10:00:00.000Z',
41
+ parts: [{ type: 'text', text: 'queued task' }],
45
42
  },
46
- visibleMessages: [],
47
43
  };
48
44
  }
49
45
 
46
+ function createSendMock(handle: NcpRunHandle | null = createRunHandle()) {
47
+ return vi.fn<TestAgentSend>(async () => handle);
48
+ }
49
+
50
50
  function createControllerParams(params: {
51
51
  isRunning: boolean;
52
+ queuedInputs?: readonly UiNcpSessionQueuedInputView[];
52
53
  send?: ReturnType<typeof createSendMock>;
53
54
  }) {
54
- const send = params.send ?? createSendMock();
55
+ const { isRunning, queuedInputs = [], send: requestedSend } = params;
56
+ const send = requestedSend ?? createSendMock();
57
+ const removeQueuedInput = vi.fn(async (id: string) =>
58
+ queuedInputs.find((item) => item.id === id) ?? null,
59
+ );
55
60
  return {
56
- agent: createAgent({ isRunning: params.isRunning, send }),
61
+ agent: {
62
+ abort: vi.fn(),
63
+ isHydrating: false,
64
+ isRunning,
65
+ isSending: false,
66
+ send,
67
+ snapshot: {
68
+ activeRun: isRunning ? { sessionId: 'session-1' } : null,
69
+ },
70
+ visibleMessages: [],
71
+ },
57
72
  inputQuery: {
58
73
  defaultModel: 'test-model',
59
74
  defaultProjectRoot: null,
@@ -90,6 +105,10 @@ function createControllerParams(params: {
90
105
  text: 'next task',
91
106
  },
92
107
  isRuntimeBlocked: false,
108
+ runQueue: {
109
+ inputs: queuedInputs,
110
+ removeQueuedInput,
111
+ },
93
112
  selectedAgentId: 'main',
94
113
  sessionKey: 'session-1',
95
114
  resetComposer: vi.fn(),
@@ -98,84 +117,57 @@ function createControllerParams(params: {
98
117
  };
99
118
  }
100
119
 
101
- describe('useSessionConversationController queued input delivery', () => {
102
- afterEach(() => {
103
- vi.useRealTimers();
104
- });
105
-
106
- it('queues a draft while the agent is running and sends it when the run becomes idle', async () => {
107
- vi.useFakeTimers({ toFake: ['Date'] });
108
- vi.setSystemTime(new Date('2026-07-05T10:00:00.000Z'));
109
- const send = createSendMock();
110
- const { result, rerender } = renderHook(
111
- ({ isRunning }) => useSessionConversationController(createControllerParams({ isRunning, send })),
112
- { initialProps: { isRunning: true } },
113
- );
120
+ describe('useSessionConversationController backend run queue', () => {
121
+ it('submits immediately while the session is running so the backend can enqueue it', async () => {
122
+ const send = createSendMock(createRunHandle({ runId: null }));
123
+ const params = createControllerParams({ isRunning: true, send });
124
+ const { result } = renderHook(() => useSessionConversationController(params));
114
125
 
115
126
  await act(async () => {
116
127
  await result.current.send();
117
128
  });
118
129
 
119
- expect(send).not.toHaveBeenCalled();
120
- expect(result.current.queuedInputs).toEqual([{ id: expect.any(String), preview: 'next task' }]);
121
-
122
- vi.setSystemTime(new Date('2026-07-05T10:01:00.000Z'));
123
- rerender({ isRunning: false });
124
-
125
- await waitFor(() => expect(send).toHaveBeenCalledTimes(1));
130
+ expect(send).toHaveBeenCalledTimes(1);
126
131
  expect(send.mock.calls[0]?.[0]).toMatchObject({
132
+ sessionId: 'session-1',
127
133
  message: expect.objectContaining({
128
134
  parts: [{ type: 'text', text: 'next task' }],
129
- timestamp: '2026-07-05T10:01:00.000Z',
130
135
  }),
131
- sessionId: 'session-1',
132
136
  });
133
- expect(send.mock.calls[0]?.[0]).not.toHaveProperty('runDelivery');
137
+ expect(params.resetComposer).toHaveBeenCalledTimes(1);
134
138
  });
135
139
 
136
- it('returns a queued draft to the composer for editing and cancels its queue entry', async () => {
137
- const send = createSendMock();
138
- const params = createControllerParams({ isRunning: true, send });
140
+ it('projects the backend queue and returns a removed item to the composer for editing', async () => {
141
+ const queuedInput = createQueuedInput();
142
+ const params = createControllerParams({ isRunning: true, queuedInputs: [queuedInput] });
139
143
  const { result } = renderHook(() => useSessionConversationController(params));
140
144
 
141
- await act(async () => {
142
- await result.current.send();
143
- });
144
- const queuedId = result.current.queuedInputs[0]?.id;
145
- expect(queuedId).toBeTruthy();
146
-
145
+ expect(result.current.queuedInputs).toEqual([
146
+ { id: queuedInput.id, preview: 'queued task' },
147
+ ]);
147
148
  act(() => {
148
- result.current.editQueuedInput(queuedId ?? '');
149
+ result.current.editQueuedInput(queuedInput.id);
149
150
  });
150
151
 
151
- expect(result.current.queuedInputs).toEqual([]);
152
- expect(params.restoreComposer).toHaveBeenCalledWith({
152
+ await waitFor(() => expect(params.runQueue.removeQueuedInput).toHaveBeenCalledWith(queuedInput.id));
153
+ expect(params.restoreComposer).toHaveBeenCalledWith(expect.objectContaining({
153
154
  attachments: [],
154
- nodes: [createTextNode('next task')],
155
155
  selectedSkills: [],
156
156
  skillRecords: [],
157
- text: 'next task',
158
- });
159
- expect(send).not.toHaveBeenCalled();
157
+ text: 'queued task',
158
+ }));
160
159
  });
161
160
 
162
- it('deletes a queued draft without restoring it to the composer', async () => {
163
- const send = createSendMock();
164
- const params = createControllerParams({ isRunning: true, send });
161
+ it('deletes through the backend queue owner without restoring the composer', async () => {
162
+ const queuedInput = createQueuedInput();
163
+ const params = createControllerParams({ isRunning: true, queuedInputs: [queuedInput] });
165
164
  const { result } = renderHook(() => useSessionConversationController(params));
166
165
 
167
- await act(async () => {
168
- await result.current.send();
169
- });
170
- const queuedId = result.current.queuedInputs[0]?.id;
171
- expect(queuedId).toBeTruthy();
172
-
173
166
  act(() => {
174
- result.current.deleteQueuedInput(queuedId ?? '');
167
+ result.current.deleteQueuedInput(queuedInput.id);
175
168
  });
176
169
 
177
- expect(result.current.queuedInputs).toEqual([]);
170
+ await waitFor(() => expect(params.runQueue.removeQueuedInput).toHaveBeenCalledWith(queuedInput.id));
178
171
  expect(params.restoreComposer).not.toHaveBeenCalled();
179
- expect(send).not.toHaveBeenCalled();
180
172
  });
181
173
  });
@@ -0,0 +1,85 @@
1
+ import type { ReactNode } from 'react';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { act, renderHook, waitFor } from '@testing-library/react';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { eventKeys, type UiNcpSessionQueuedInputsView } from '@nextclaw/client-sdk';
6
+
7
+ import { useSessionRunQueue } from '@/features/chat/features/conversation/hooks/use-session-run-queue';
8
+ import { nextclawClient } from '@/shared/lib/api';
9
+
10
+ function createQueue(sessionId: string, text: string): UiNcpSessionQueuedInputsView {
11
+ return {
12
+ sessionId,
13
+ inputs: [{
14
+ id: `queued-${sessionId}`,
15
+ sessionId,
16
+ enqueuedAt: '2026-07-22T10:00:00.000Z',
17
+ metadata: {},
18
+ message: {
19
+ id: `message-${sessionId}`,
20
+ sessionId,
21
+ role: 'user',
22
+ status: 'final',
23
+ timestamp: '2026-07-22T10:00:00.000Z',
24
+ parts: [{ type: 'text', text }],
25
+ },
26
+ }],
27
+ };
28
+ }
29
+
30
+ function createWrapper() {
31
+ const queryClient = new QueryClient({
32
+ defaultOptions: { queries: { retry: false } },
33
+ });
34
+ return function Wrapper({ children }: { readonly children: ReactNode }) {
35
+ return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
36
+ };
37
+ }
38
+
39
+ describe('useSessionRunQueue', () => {
40
+ afterEach(() => {
41
+ vi.restoreAllMocks();
42
+ });
43
+
44
+ it('keys backend queue data by session instead of sharing one frontend queue', async () => {
45
+ vi.spyOn(nextclawClient.sessions, 'listQueuedInputs').mockImplementation(async (sessionId) =>
46
+ createQueue(sessionId, sessionId === 'session-1' ? 'first session' : 'second session'),
47
+ );
48
+ const { result, rerender } = renderHook(
49
+ ({ sessionKey }) => useSessionRunQueue(sessionKey),
50
+ { initialProps: { sessionKey: 'session-1' }, wrapper: createWrapper() },
51
+ );
52
+
53
+ await waitFor(() => expect(result.current.inputs[0]?.message.parts[0]).toMatchObject({ text: 'first session' }));
54
+ rerender({ sessionKey: 'session-2' });
55
+ await waitFor(() => expect(result.current.inputs[0]?.message.parts[0]).toMatchObject({ text: 'second session' }));
56
+ expect(nextclawClient.sessions.listQueuedInputs).toHaveBeenCalledWith('session-1');
57
+ expect(nextclawClient.sessions.listQueuedInputs).toHaveBeenCalledWith('session-2');
58
+ });
59
+
60
+ it('refetches only when the matching session queue changes', async () => {
61
+ const listQueuedInputs = vi.spyOn(nextclawClient.sessions, 'listQueuedInputs')
62
+ .mockResolvedValue(createQueue('session-1', 'queued'));
63
+ const { unmount } = renderHook(
64
+ () => useSessionRunQueue('session-1'),
65
+ { wrapper: createWrapper() },
66
+ );
67
+ await waitFor(() => expect(listQueuedInputs).toHaveBeenCalledTimes(1));
68
+
69
+ act(() => {
70
+ nextclawClient.eventBus.emit(eventKeys.sessionRunQueueUpdated, {
71
+ sessionKey: 'session-2',
72
+ });
73
+ });
74
+ await new Promise((resolve) => setTimeout(resolve, 0));
75
+ expect(listQueuedInputs).toHaveBeenCalledTimes(1);
76
+
77
+ act(() => {
78
+ nextclawClient.eventBus.emit(eventKeys.sessionRunQueueUpdated, {
79
+ sessionKey: 'session-1',
80
+ });
81
+ });
82
+ await waitFor(() => expect(listQueuedInputs).toHaveBeenCalledTimes(2));
83
+ unmount();
84
+ });
85
+ });