@nextclaw/ui 0.12.13 → 0.12.14

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.
@@ -11,7 +11,8 @@ import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
11
11
  const mocks = vi.hoisted(() => ({
12
12
  deleteSession: vi.fn(),
13
13
  goToProviders: vi.fn(),
14
- createSession: vi.fn(),
14
+ createSession: vi.fn(() => "draft-session-2"),
15
+ goToSession: vi.fn(),
15
16
  setSelectedAgentId: vi.fn(),
16
17
  setPendingSessionType: vi.fn(),
17
18
  stickyBottomScroll: vi.fn(() => ({
@@ -79,6 +80,9 @@ vi.mock("@/features/chat/components/chat-welcome", () => ({
79
80
 
80
81
  vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
81
82
  usePresenter: () => ({
83
+ chatUiManager: {
84
+ goToSession: mocks.goToSession,
85
+ },
82
86
  chatThreadManager: {
83
87
  deleteSession: mocks.deleteSession,
84
88
  goToProviders: mocks.goToProviders,
@@ -158,6 +162,8 @@ describe("ChatConversationPanel", () => {
158
162
  mocks.deleteSession.mockReset();
159
163
  mocks.goToProviders.mockReset();
160
164
  mocks.createSession.mockReset();
165
+ mocks.createSession.mockReturnValue("draft-session-2");
166
+ mocks.goToSession.mockReset();
161
167
  mocks.setSelectedAgentId.mockReset();
162
168
  mocks.setPendingSessionType.mockReset();
163
169
  mocks.stickyBottomScroll.mockClear();
@@ -233,6 +239,17 @@ describe("ChatConversationPanel", () => {
233
239
  expect(onBackToList).toHaveBeenCalledTimes(1);
234
240
  });
235
241
 
242
+ it("opens the new draft session immediately when mobile welcome creates a session", async () => {
243
+ const user = userEvent.setup();
244
+
245
+ render(<ChatConversationPanel layoutMode="mobile" />);
246
+
247
+ await user.click(screen.getByRole("button", { name: "create draft session" }));
248
+
249
+ expect(mocks.createSession).toHaveBeenCalledWith("native");
250
+ expect(mocks.goToSession).toHaveBeenCalledWith("draft-session-2");
251
+ });
252
+
236
253
  it("shows the selected session project badge and more actions trigger", () => {
237
254
  useChatThreadStore.setState({
238
255
  snapshot: {
@@ -272,7 +272,8 @@ export function ChatConversationPanel({
272
272
  resolveDraftAgent(snapshot.agentId ?? "main"),
273
273
  defaultSessionType,
274
274
  );
275
- presenter.chatSessionListManager.createSession(sessionType);
275
+ const sessionKey = presenter.chatSessionListManager.createSession(sessionType);
276
+ if (layoutMode === "mobile") presenter.chatUiManager.goToSession(sessionKey);
276
277
  };
277
278
  const selectDraftAgent = (agentId: string) => {
278
279
  presenter.chatSessionListManager.setSelectedAgentId(agentId);
@@ -7,7 +7,8 @@ import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
7
7
  import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
8
8
 
9
9
  const mocks = vi.hoisted(() => ({
10
- createSession: vi.fn(),
10
+ createSession: vi.fn(() => 'draft-session-key'),
11
+ goToSession: vi.fn(),
11
12
  setQuery: vi.fn(),
12
13
  setListMode: vi.fn(),
13
14
  selectSession: vi.fn(),
@@ -28,6 +29,9 @@ function createSessionItem(
28
29
 
29
30
  vi.mock('@/features/chat/components/providers/chat-presenter.provider', () => ({
30
31
  usePresenter: () => ({
32
+ chatUiManager: {
33
+ goToSession: mocks.goToSession,
34
+ },
31
35
  chatSessionListManager: {
32
36
  createSession: mocks.createSession,
33
37
  setQuery: mocks.setQuery,
@@ -115,6 +119,8 @@ vi.mock('@/features/system-status', () => ({
115
119
 
116
120
  function resetSidebarTestState() {
117
121
  mocks.createSession.mockReset();
122
+ mocks.createSession.mockReturnValue('draft-session-key');
123
+ mocks.goToSession.mockReset();
118
124
  mocks.setQuery.mockReset();
119
125
  mocks.setListMode.mockReset();
120
126
  mocks.selectSession.mockReset();
@@ -228,6 +234,7 @@ describe('ChatSidebar create and list basics', () => {
228
234
 
229
235
  expect(mocks.setQuery).toHaveBeenCalledWith('release notes');
230
236
  expect(mocks.createSession).toHaveBeenCalledWith('codex');
237
+ expect(mocks.goToSession).toHaveBeenCalledWith('draft-session-key');
231
238
  });
232
239
 
233
240
  it('creates the default session directly from the compact mobile add button when no menu is needed', () => {
@@ -248,6 +255,7 @@ describe('ChatSidebar create and list basics', () => {
248
255
  fireEvent.click(screen.getByRole('button', { name: 'New Task' }));
249
256
 
250
257
  expect(mocks.createSession).toHaveBeenCalledWith('native');
258
+ expect(mocks.goToSession).toHaveBeenCalledWith('draft-session-key');
251
259
  });
252
260
 
253
261
  it('shows a session type badge for non-native sessions in the list', () => {
@@ -408,6 +416,7 @@ describe('ChatSidebar project-first mode', () => {
408
416
  fireEvent.click(screen.getByText('Codex'));
409
417
 
410
418
  expect(mocks.createSession).toHaveBeenCalledWith('codex', '/tmp/project-beta');
419
+ expect(mocks.goToSession).not.toHaveBeenCalled();
411
420
  });
412
421
 
413
422
  it('creates immediately when there is only one available runtime type', () => {
@@ -447,6 +456,41 @@ describe('ChatSidebar project-first mode', () => {
447
456
  fireEvent.click(screen.getByRole('button', { name: 'New Task · project-gamma' }));
448
457
 
449
458
  expect(mocks.createSession).toHaveBeenCalledWith('native', '/tmp/project-gamma');
459
+ expect(mocks.goToSession).not.toHaveBeenCalled();
460
+ });
461
+
462
+ it('opens the draft detail after creating a project-bound session on mobile', () => {
463
+ useChatSessionListStore.setState({
464
+ snapshot: {
465
+ ...useChatSessionListStore.getState().snapshot,
466
+ listMode: 'project-first'
467
+ }
468
+ });
469
+ mocks.sessionItems = [
470
+ createSessionItem({
471
+ key: 'session:project-mobile-1',
472
+ createdAt: '2026-03-19T09:00:00.000Z',
473
+ updatedAt: '2026-03-19T11:05:00.000Z',
474
+ label: 'Grouped Mobile Task',
475
+ projectRoot: '/tmp/project-mobile',
476
+ projectName: 'project-mobile',
477
+ sessionType: 'native',
478
+ sessionTypeMutable: false,
479
+ messageCount: 2
480
+ })
481
+ ];
482
+
483
+ render(
484
+ <MemoryRouter>
485
+ <ChatSidebar variant="mobile" />
486
+ </MemoryRouter>
487
+ );
488
+
489
+ fireEvent.click(screen.getByRole('button', { name: 'New Task · project-mobile' }));
490
+ fireEvent.click(screen.getByText('Codex'));
491
+
492
+ expect(mocks.createSession).toHaveBeenCalledWith('codex', '/tmp/project-mobile');
493
+ expect(mocks.goToSession).toHaveBeenCalledWith('draft-session-key');
450
494
  });
451
495
  });
452
496
 
@@ -8,10 +8,7 @@ import { useChatSidebarSessionLabelEditor } from '@/features/chat/hooks/use-chat
8
8
  import { useNcpSessionListView, type NcpSessionListItemView } from '@/features/chat/hooks/use-ncp-session-list-view';
9
9
  import { usePresenter } from '@/features/chat/components/providers/chat-presenter.provider';
10
10
  import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
11
- import {
12
- shouldShowUnreadSessionIndicator,
13
- useChatSessionListStore
14
- } from '@/features/chat/stores/chat-session-list.store';
11
+ import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
15
12
  import { useSystemStatus } from '@/features/system-status';
16
13
  import { useAgents } from '@/shared/hooks/use-agents';
17
14
  import { getSessionProjectName } from '@/shared/lib/session-project';
@@ -230,32 +227,33 @@ export function ChatSidebar({
230
227
  setLanguage(nextLang);
231
228
  window.location.reload();
232
229
  };
233
- const renderSessionItem = (item: NcpSessionListItemView) =>
234
- (
235
- <ChatSidebarSessionEntry
236
- key={item.session.key}
237
- item={item}
238
- selectedSessionKey={listSnapshot.selectedSessionKey}
239
- optimisticReadAtBySessionKey={optimisticReadAtBySessionKey}
240
- agentsById={agentsById}
241
- childSessionsByParentKey={childSessionsByParentKey}
242
- editingSessionKey={editingSessionKey}
243
- draftLabel={draftLabel}
244
- savingSessionKey={savingSessionKey}
245
- sessionTitle={sessionTitle}
246
- onSelectSession={presenter.chatSessionListManager.selectSession}
247
- onOpenChildSessions={(parentSessionKey, activeChildSessionKey) =>
248
- presenter.chatThreadManager.openChildSessionPanel({
249
- parentSessionKey,
250
- activeChildSessionKey,
251
- })
252
- }
253
- onStartEditingSessionLabel={startEditingSessionLabel}
254
- onDraftLabelChange={setDraftLabel}
255
- onSaveSessionLabel={saveSessionLabel}
256
- onCancelEditingSessionLabel={cancelEditingSessionLabel}
257
- />
258
- );
230
+ const renderSessionItem = (item: NcpSessionListItemView) => (
231
+ <ChatSidebarSessionEntry
232
+ key={item.session.key}
233
+ item={item}
234
+ selectedSessionKey={listSnapshot.selectedSessionKey}
235
+ optimisticReadAtBySessionKey={optimisticReadAtBySessionKey}
236
+ agentsById={agentsById}
237
+ childSessionsByParentKey={childSessionsByParentKey}
238
+ editingSessionKey={editingSessionKey}
239
+ draftLabel={draftLabel}
240
+ savingSessionKey={savingSessionKey}
241
+ sessionTitle={sessionTitle}
242
+ onSelectSession={presenter.chatSessionListManager.selectSession}
243
+ onOpenChildSessions={(parentSessionKey, activeChildSessionKey) => presenter.chatThreadManager.openChildSessionPanel({ parentSessionKey, activeChildSessionKey })}
244
+ onStartEditingSessionLabel={startEditingSessionLabel}
245
+ onDraftLabelChange={setDraftLabel}
246
+ onSaveSessionLabel={saveSessionLabel}
247
+ onCancelEditingSessionLabel={cancelEditingSessionLabel}
248
+ />
249
+ );
250
+ const createSessionAndOpenIfNeeded = (sessionType: string, projectRoot?: string | null) => {
251
+ const sessionKey = typeof projectRoot === "string"
252
+ ? presenter.chatSessionListManager.createSession(sessionType, projectRoot)
253
+ : presenter.chatSessionListManager.createSession(sessionType);
254
+ if (isMobileVariant) presenter.chatUiManager.goToSession(sessionKey);
255
+ };
256
+
259
257
  return (
260
258
  <aside
261
259
  className={cn(
@@ -282,7 +280,7 @@ export function ChatSidebar({
282
280
  nonDefaultSessionTypeOptions={nonDefaultSessionTypeOptions}
283
281
  isCreateMenuOpen={isCreateMenuOpen}
284
282
  onCreateMenuOpenChange={setIsCreateMenuOpen}
285
- onCreateSession={presenter.chatSessionListManager.createSession}
283
+ onCreateSession={createSessionAndOpenIfNeeded}
286
284
  onQueryChange={presenter.chatSessionListManager.setQuery}
287
285
  />
288
286
  ) : (
@@ -293,7 +291,7 @@ export function ChatSidebar({
293
291
  nonDefaultSessionTypeOptions={nonDefaultSessionTypeOptions}
294
292
  isCreateMenuOpen={isCreateMenuOpen}
295
293
  onCreateMenuOpenChange={setIsCreateMenuOpen}
296
- onCreateSession={presenter.chatSessionListManager.createSession}
294
+ onCreateSession={createSessionAndOpenIfNeeded}
297
295
  onQueryChange={presenter.chatSessionListManager.setQuery}
298
296
  />
299
297
  )}
@@ -301,18 +299,11 @@ export function ChatSidebar({
301
299
  {!isMobileVariant ? (
302
300
  <div className="px-3 pb-2">
303
301
  <ul className="space-y-0.5">
304
- {navItems.map((item) => {
305
- return (
306
- <li key={item.target}>
307
- <SidebarNavLinkItem
308
- to={item.target}
309
- label={item.label()}
310
- icon={item.icon}
311
- density="compact"
312
- />
313
- </li>
314
- );
315
- })}
302
+ {navItems.map((item) => (
303
+ <li key={item.target}>
304
+ <SidebarNavLinkItem to={item.target} label={item.label()} icon={item.icon} density="compact" />
305
+ </li>
306
+ ))}
316
307
  </ul>
317
308
  </div>
318
309
  ) : null}
@@ -346,7 +337,7 @@ export function ChatSidebar({
346
337
  defaultSessionType={defaultSessionType}
347
338
  sessionTypeOptions={inputSnapshot.sessionTypeOptions}
348
339
  renderSessionItem={renderSessionItem}
349
- onCreateSession={presenter.chatSessionListManager.createSession}
340
+ onCreateSession={createSessionAndOpenIfNeeded}
350
341
  />
351
342
  )
352
343
  ) : groups.length === 0 ? (