@nextclaw/ui 0.12.24 → 0.12.25

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 (206) hide show
  1. package/CHANGELOG.md +68 -29
  2. package/dist/assets/api-DGD9_Bg4.js +15 -0
  3. package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
  4. package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
  5. package/dist/assets/channels-list-page-FJDuPwU6.js +8 -0
  6. package/dist/assets/chat-page-D1fMNBrT.js +1 -0
  7. package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
  8. package/dist/assets/cpu-DPPwMzoC.js +3 -0
  9. package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
  10. package/dist/assets/desktop-kk7qvZ-v.js +3 -0
  11. package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
  12. package/dist/assets/{dialog-C3D7Be0p.js → dialog-BKo0RItd.js} +1 -1
  13. package/dist/assets/{dist-CPlbUgwU.js → dist-CFiwgaLs.js} +1 -1
  14. package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
  15. package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
  16. package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
  17. package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
  18. package/dist/assets/download-CMM8po31.js +1 -0
  19. package/dist/assets/{es2015-xqN1slyW.js → es2015-BhznEEyJ.js} +1 -1
  20. package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
  21. package/dist/assets/i18n-D1144VAA.js +1 -0
  22. package/dist/assets/index-D-AAMKCt.js +103 -0
  23. package/dist/assets/index-DnBeV2Xm.css +1 -0
  24. package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
  25. package/dist/assets/marketplace-page-BrCLRIc4.js +105 -0
  26. package/dist/assets/marketplace-page-odDpPYEs.js +1 -0
  27. package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +1 -0
  28. package/dist/assets/mcp-marketplace-page-DIq_SpMe.js +40 -0
  29. package/dist/assets/model-config-Bc6VVnxy.js +1 -0
  30. package/dist/assets/{notice-card-BFDbKQDA.js → notice-card-Dr6xCwva.js} +1 -1
  31. package/dist/assets/play-AqrNslHI.js +1 -0
  32. package/dist/assets/plus-B-YHtTNC.js +1 -0
  33. package/dist/assets/{popover-B86Dbfhf.js → popover-BDFNiLlg.js} +1 -1
  34. package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
  35. package/dist/assets/providers-list-DN0tvISH.js +1 -0
  36. package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
  37. package/dist/assets/remote-Dr3jcfWP.js +1 -0
  38. package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
  39. package/dist/assets/runtime-config-page-CRWOwBbl.js +1 -0
  40. package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
  41. package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
  42. package/dist/assets/search-config-C4c1yZSP.js +1 -0
  43. package/dist/assets/secrets-config-zAF30YfO.js +3 -0
  44. package/dist/assets/{select-CJ0wbo3D.js → select-BUTwE_lC.js} +1 -1
  45. package/dist/assets/{setting-row-D1Yygqp7.js → setting-row-BavcnXw1.js} +1 -1
  46. package/dist/assets/settings-MWL2SMyk.js +1 -0
  47. package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
  48. package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
  49. package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
  50. package/dist/assets/{tag-chip-FrkmkT8r.js → tag-chip-Dm2Lqnpu.js} +1 -1
  51. package/dist/assets/use-config-Cyv5IuSt.js +1 -0
  52. package/dist/assets/use-infinite-scroll-loader-Cvz8ZteY.js +1 -0
  53. package/dist/assets/x-BeyYA_h6.js +1 -0
  54. package/dist/index.html +29 -40
  55. package/package.json +9 -9
  56. package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
  57. package/src/app/components/theme-provider.tsx +1 -0
  58. package/src/app/configs/app-navigation.config.ts +0 -6
  59. package/src/app/index.tsx +4 -7
  60. package/src/features/agents/components/agents-page.test.tsx +25 -15
  61. package/src/features/agents/components/agents-page.tsx +133 -172
  62. package/src/features/channels/components/config/channel-form.test.tsx +1 -0
  63. package/src/features/channels/components/config/channel-form.tsx +4 -3
  64. package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
  65. package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
  66. package/src/features/channels/index.ts +1 -1
  67. package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
  68. package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
  69. package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
  70. package/src/features/chat/components/chat-session-workspace-panel.tsx +34 -2
  71. package/src/features/chat/components/chat-sidebar-session-item.tsx +9 -3
  72. package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +71 -0
  73. package/src/features/chat/components/conversation/chat-conversation-header.tsx +6 -0
  74. package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +181 -61
  75. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +54 -23
  76. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
  77. package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +26 -5
  78. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
  79. package/src/features/chat/components/layout/chat-sidebar.test.tsx +45 -8
  80. package/src/features/chat/components/layout/chat-sidebar.tsx +29 -46
  81. package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
  82. package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
  83. package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +14 -0
  84. package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
  85. package/src/features/chat/hooks/use-ncp-chat-page-data.ts +1 -1
  86. package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
  87. package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
  88. package/src/features/chat/managers/chat-session-list.manager.test.ts +7 -9
  89. package/src/features/chat/managers/chat-session-list.manager.ts +5 -10
  90. package/src/features/chat/managers/ncp-chat-input.manager.test.ts +0 -2
  91. package/src/features/chat/managers/ncp-chat-presenter.manager.ts +6 -0
  92. package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
  93. package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
  94. package/src/features/chat/pages/ncp-chat-page.tsx +5 -4
  95. package/src/features/chat/stores/chat-session-list.store.ts +0 -2
  96. package/src/features/chat/stores/chat-thread.store.ts +4 -0
  97. package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
  98. package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
  99. package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
  100. package/src/features/chat/utils/ncp-session-adapter.utils.ts +32 -0
  101. package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
  102. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
  103. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
  104. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
  105. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
  106. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
  107. package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
  108. package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
  109. package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
  110. package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
  111. package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
  112. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  113. package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
  114. package/src/features/marketplace/components/marketplace-page.tsx +154 -132
  115. package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
  116. package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
  117. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
  118. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
  119. package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
  120. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
  121. package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
  122. package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
  123. package/src/features/system-status/components/runtime-control-card.tsx +7 -6
  124. package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
  125. package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
  126. package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
  127. package/src/features/system-status/utils/system-status.utils.ts +31 -6
  128. package/src/index.css +8 -0
  129. package/src/platforms/desktop/components/desktop-app-shell.test.tsx +67 -0
  130. package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
  131. package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
  132. package/src/platforms/desktop/index.ts +6 -0
  133. package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
  134. package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
  135. package/src/shared/components/common/brand-header.tsx +36 -16
  136. package/src/shared/components/config/provider-form-support.ts +2 -22
  137. package/src/shared/components/cron-config.tsx +12 -58
  138. package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
  139. package/src/shared/components/ui/select.tsx +19 -7
  140. package/src/shared/lib/api/channel-auth.types.ts +1 -0
  141. package/src/shared/lib/api/ncp-session.types.ts +9 -0
  142. package/src/shared/lib/api/types.ts +12 -1
  143. package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
  144. package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
  145. package/src/shared/lib/cron/index.ts +1 -0
  146. package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
  147. package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
  148. package/src/shared/lib/i18n/index.ts +20 -59
  149. package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
  150. package/src/shared/lib/provider-models/index.test.ts +39 -0
  151. package/src/shared/lib/provider-models/index.ts +1 -3
  152. package/src/shared/lib/ui-document-title/index.ts +0 -1
  153. package/tsconfig.json +1 -0
  154. package/vite.config.ts +1 -1
  155. package/vitest.config.ts +1 -1
  156. package/dist/assets/api-D2xRKmZd.js +0 -15
  157. package/dist/assets/app-manager-provider-CNaZboG4.js +0 -1
  158. package/dist/assets/app-navigation.config-Ihhrrt--.js +0 -1
  159. package/dist/assets/channels-list-page-p26lgxLk.js +0 -8
  160. package/dist/assets/chat-Dkh2qtuz.js +0 -61
  161. package/dist/assets/chat-page-DoTmE2wx.js +0 -1
  162. package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
  163. package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
  164. package/dist/assets/desktop-update-config-DlpzDfKM.js +0 -1
  165. package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
  166. package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
  167. package/dist/assets/doc-browser-p82AdNO-.js +0 -1
  168. package/dist/assets/folder-CeJKPx5P.js +0 -1
  169. package/dist/assets/hash-BqxRTZW5.js +0 -1
  170. package/dist/assets/i18n-DnTGDIRw.js +0 -1
  171. package/dist/assets/index-D8MKmXtO.css +0 -1
  172. package/dist/assets/index-pBvbJ5Mt.js +0 -2
  173. package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
  174. package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
  175. package/dist/assets/logos-C4sYP1Vl.js +0 -1
  176. package/dist/assets/marketplace-page-Cql0kDi-.js +0 -1
  177. package/dist/assets/marketplace-page-m4P5g_Ht.js +0 -49
  178. package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +0 -1
  179. package/dist/assets/mcp-marketplace-page-ByzBQZcx.js +0 -40
  180. package/dist/assets/message-square-z_osm9c0.js +0 -1
  181. package/dist/assets/model-config-Dbr_0APb.js +0 -1
  182. package/dist/assets/play-Dv6Nr1Ew.js +0 -1
  183. package/dist/assets/plus-D8eKFY7h.js +0 -1
  184. package/dist/assets/provider-scoped-model-input-DFm6N2f7.js +0 -1
  185. package/dist/assets/providers-list-BJcLOjun.js +0 -1
  186. package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
  187. package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
  188. package/dist/assets/remote-BOxo9iwd.js +0 -1
  189. package/dist/assets/runtime-config-page-CjLhnbSl.js +0 -1
  190. package/dist/assets/search-config-J4Htco-P.js +0 -1
  191. package/dist/assets/secrets-config-CUdERjco.js +0 -3
  192. package/dist/assets/sessions-config-page-DpK991fs.js +0 -2
  193. package/dist/assets/settings-drbWqzA4.js +0 -1
  194. package/dist/assets/skeleton-BK1SOSRA.js +0 -1
  195. package/dist/assets/theme-provider-0hxjiPc_.js +0 -2
  196. package/dist/assets/tooltip-Cj4yA0gH.js +0 -1
  197. package/dist/assets/trash-2-CBsHCfqq.js +0 -1
  198. package/dist/assets/use-config-38Ur-89i.js +0 -1
  199. package/dist/assets/use-confirm-dialog-DPQThaeU.js +0 -1
  200. package/dist/assets/use-infinite-scroll-loader-5Gf1xQi7.js +0 -1
  201. package/dist/assets/use-viewport-layout-D1XzKeip.js +0 -1
  202. package/dist/assets/x-CM-XDMpk.js +0 -1
  203. package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
  204. package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
  205. package/src/features/chat/pages/sessions-config-page.tsx +0 -192
  206. /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
@@ -70,6 +70,28 @@ describe('adaptNcpSessionSummary', () => {
70
70
  });
71
71
  });
72
72
 
73
+ it('maps session activity preview metadata into the session entry', () => {
74
+ const adapted = adaptNcpSessionSummary(
75
+ createSummary({
76
+ metadata: {
77
+ last_activity_preview: {
78
+ state: 'completed',
79
+ replyText: '已经整理好方案',
80
+ statusText: '工具调用完成',
81
+ timestamp: '2026-05-16T01:00:00.000Z',
82
+ },
83
+ },
84
+ }),
85
+ );
86
+
87
+ expect(adapted.activityPreview).toEqual({
88
+ state: 'completed',
89
+ replyText: '已经整理好方案',
90
+ statusText: '工具调用完成',
91
+ timestamp: '2026-05-16T01:00:00.000Z',
92
+ });
93
+ });
94
+
73
95
  it('does not hydrate context window metadata from persisted session summaries', () => {
74
96
  const adapted = adaptNcpSessionSummary(
75
97
  createSummary({
@@ -3,6 +3,7 @@ import type { NcpMessagePart } from '@nextclaw/ncp';
3
3
  import type {
4
4
  NcpMessageView,
5
5
  NcpSessionSummaryView,
6
+ SessionActivityPreviewView,
6
7
  SessionEntryView,
7
8
  ThinkingLevel
8
9
  } from '@/shared/lib/api';
@@ -13,6 +14,12 @@ import {
13
14
  } from '@/shared/lib/session-project';
14
15
 
15
16
  const THINKING_LEVEL_SET = new Set<string>(['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh']);
17
+ const SESSION_ACTIVITY_PREVIEW_STATE_SET = new Set<SessionActivityPreviewView['state']>([
18
+ 'running',
19
+ 'completed',
20
+ 'failed',
21
+ 'idle'
22
+ ]);
16
23
 
17
24
  function stringifyUnknown(value: unknown): string {
18
25
  if (typeof value === 'string') {
@@ -46,6 +53,28 @@ function readMetadata(summary: NcpSessionSummaryView): Record<string, unknown> |
46
53
  return metadata as Record<string, unknown>;
47
54
  }
48
55
 
56
+ function readNcpSessionActivityPreview(summary: NcpSessionSummaryView): SessionActivityPreviewView | null {
57
+ const metadata = readMetadata(summary);
58
+ const preview = metadata?.last_activity_preview;
59
+ if (!preview || typeof preview !== 'object' || Array.isArray(preview)) {
60
+ return null;
61
+ }
62
+ const previewRecord = preview as Record<string, unknown>;
63
+ const { state } = previewRecord;
64
+ const timestamp = readOptionalString(previewRecord.timestamp);
65
+ if (!SESSION_ACTIVITY_PREVIEW_STATE_SET.has(state as SessionActivityPreviewView['state']) || !timestamp) {
66
+ return null;
67
+ }
68
+ const statusText = readOptionalString(previewRecord.statusText);
69
+ const replyText = readOptionalString(previewRecord.replyText);
70
+ return {
71
+ state: state as SessionActivityPreviewView['state'],
72
+ timestamp,
73
+ ...(statusText ? { statusText } : {}),
74
+ ...(replyText ? { replyText } : {})
75
+ };
76
+ }
77
+
49
78
  export function readNcpSessionPreferredModel(summary: NcpSessionSummaryView): string | null {
50
79
  const metadata = readMetadata(summary);
51
80
  if (!metadata) {
@@ -286,6 +315,7 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
286
315
  const parentSessionId = readNcpParentSessionId(summary);
287
316
  const spawnedByRequestId = readNcpSpawnedByRequestId(summary);
288
317
  const isPromotedChildSession = readPromotedChildSession(summary);
318
+ const activityPreview = readNcpSessionActivityPreview(summary);
289
319
  return {
290
320
  key: summary.sessionId,
291
321
  createdAt: summary.createdAt ?? summary.updatedAt,
@@ -301,10 +331,12 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
301
331
  ...(projectName ? { projectName } : {}),
302
332
  sessionType: readNcpSessionType(summary),
303
333
  sessionTypeMutable: false,
334
+ status: summary.status ?? 'idle',
304
335
  isChildSession: Boolean(parentSessionId),
305
336
  ...(isPromotedChildSession ? { isPromotedChildSession } : {}),
306
337
  ...(parentSessionId ? { parentSessionId } : {}),
307
338
  ...(spawnedByRequestId ? { spawnedByRequestId } : {}),
339
+ ...(activityPreview ? { activityPreview } : {}),
308
340
  messageCount: summary.messageCount
309
341
  };
310
342
  }
@@ -0,0 +1,235 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { MarketplacePage } from "@/features/marketplace";
4
+ import type {
5
+ MarketplaceInstalledView,
6
+ MarketplaceItemSummary,
7
+ MarketplaceListView,
8
+ } from "@/shared/lib/api";
9
+
10
+ type ItemsQueryState = {
11
+ data?: MarketplaceListView;
12
+ isLoading: boolean;
13
+ isFetching: boolean;
14
+ isFetchingNextPage?: boolean;
15
+ hasNextPage?: boolean;
16
+ isError: boolean;
17
+ error: Error | null;
18
+ fetchNextPage?: () => Promise<unknown>;
19
+ };
20
+
21
+ const mocks = vi.hoisted(() => ({
22
+ navigate: vi.fn(),
23
+ docOpen: vi.fn(),
24
+ routeParams: {} as { scene?: string },
25
+ itemsQuery: null as unknown as ItemsQueryState,
26
+ }));
27
+
28
+ vi.mock("react-router-dom", async () => {
29
+ const actual = await vi.importActual("react-router-dom");
30
+ return {
31
+ ...(actual as object),
32
+ useNavigate: () => mocks.navigate,
33
+ useParams: () => mocks.routeParams,
34
+ };
35
+ });
36
+
37
+ vi.mock("@/shared/components/doc-browser", () => ({
38
+ useDocBrowser: () => ({
39
+ open: mocks.docOpen,
40
+ }),
41
+ }));
42
+
43
+ vi.mock("@/app/components/i18n-provider", () => ({
44
+ useI18n: () => ({
45
+ language: "en",
46
+ }),
47
+ }));
48
+
49
+ vi.mock("@/shared/hooks/use-confirm-dialog", () => ({
50
+ useConfirmDialog: () => ({
51
+ confirm: vi.fn(),
52
+ ConfirmDialog: () => null,
53
+ }),
54
+ }));
55
+
56
+ vi.mock("@/features/marketplace/hooks/use-marketplace", () => ({
57
+ useMarketplaceItems: () => mocks.itemsQuery,
58
+ useMarketplaceSkillScenes: () => ({
59
+ data: {
60
+ scenes: [
61
+ {
62
+ scene: "development-debugging",
63
+ title: "Development",
64
+ description: "Review, debug, analyze, and verify delivery work.",
65
+ },
66
+ ],
67
+ },
68
+ isLoading: false,
69
+ isFetching: false,
70
+ isError: false,
71
+ error: null,
72
+ }),
73
+ useMarketplaceSkillSceneCounts: () => new Map([["development-debugging", 2]]),
74
+ useMarketplaceInstalled: () => ({
75
+ data: {
76
+ type: "skill",
77
+ total: 0,
78
+ specs: [],
79
+ records: [],
80
+ } satisfies MarketplaceInstalledView,
81
+ isLoading: false,
82
+ isFetching: false,
83
+ isError: false,
84
+ error: null,
85
+ }),
86
+ useInstallMarketplaceItem: () => ({
87
+ mutateAsync: vi.fn(),
88
+ }),
89
+ useManageMarketplaceItem: () => ({
90
+ mutateAsync: vi.fn(),
91
+ }),
92
+ }));
93
+
94
+ function createMarketplaceItem(
95
+ overrides: Partial<MarketplaceItemSummary> = {},
96
+ ): MarketplaceItemSummary {
97
+ return {
98
+ id: "skill-web-search",
99
+ slug: "web-search",
100
+ type: "skill",
101
+ name: "Web Search",
102
+ summary: "Search the web from the marketplace",
103
+ summaryI18n: { en: "Search the web from the marketplace" },
104
+ tags: ["search"],
105
+ author: "NextClaw",
106
+ install: {
107
+ kind: "marketplace",
108
+ spec: "@nextclaw/web-search",
109
+ command: "nextclaw skills install @nextclaw/web-search",
110
+ },
111
+ updatedAt: "2026-03-17T00:00:00.000Z",
112
+ ...overrides,
113
+ };
114
+ }
115
+
116
+ function createSceneItems(): MarketplaceItemSummary[] {
117
+ return [
118
+ createMarketplaceItem({
119
+ id: "skill-code-review",
120
+ slug: "code-review",
121
+ name: "Code Review",
122
+ summary: "Review code changes",
123
+ tags: ["code", "review"],
124
+ }),
125
+ createMarketplaceItem({
126
+ id: "skill-debug-lab",
127
+ slug: "debug-lab",
128
+ name: "Debug Lab",
129
+ summary: "Debug runtime failures",
130
+ tags: ["debug"],
131
+ }),
132
+ createMarketplaceItem({
133
+ id: "skill-calendar-sync",
134
+ slug: "calendar-sync",
135
+ name: "Calendar Sync",
136
+ summary: "Sync calendar events",
137
+ tags: ["calendar"],
138
+ }),
139
+ createMarketplaceItem({
140
+ id: "skill-writing-room",
141
+ slug: "writing-room",
142
+ name: "Writing Room",
143
+ summary: "Draft long-form writing",
144
+ tags: ["writing"],
145
+ }),
146
+ ];
147
+ }
148
+
149
+ function createItemsQuery(items: MarketplaceItemSummary[]) {
150
+ return {
151
+ data: {
152
+ total: items.length,
153
+ page: 1,
154
+ pageSize: 20,
155
+ totalPages: 1,
156
+ sort: "relevance",
157
+ items,
158
+ } satisfies MarketplaceListView,
159
+ isLoading: false,
160
+ isFetching: false,
161
+ isError: false,
162
+ error: null,
163
+ hasNextPage: false,
164
+ fetchNextPage: vi.fn(),
165
+ };
166
+ }
167
+
168
+ describe("Marketplace curated scene routes", () => {
169
+ beforeEach(() => {
170
+ mocks.navigate.mockReset();
171
+ mocks.docOpen.mockReset();
172
+ mocks.routeParams = {};
173
+ mocks.itemsQuery = createItemsQuery(createSceneItems());
174
+ });
175
+
176
+ it("opens curated goals through a scene route", async () => {
177
+ const user = userEvent.setup();
178
+ const { container } = render(<MarketplacePage forcedType="skills" />);
179
+
180
+ await user.click(screen.getByRole("button", { name: /Development/ }));
181
+
182
+ expect(screen.getByText("2 skills")).toBeTruthy();
183
+ expect(mocks.navigate).toHaveBeenCalledWith(
184
+ "/skills/scenes/development-debugging",
185
+ );
186
+ expect(container.querySelector("input")?.getAttribute("value") ?? "").toBe("");
187
+ });
188
+
189
+ it("renders scene routes as adaptive grids without the catalog shell", async () => {
190
+ const user = userEvent.setup();
191
+ mocks.routeParams = { scene: "development-debugging" };
192
+ mocks.itemsQuery = createItemsQuery(createSceneItems().slice(0, 2));
193
+
194
+ render(<MarketplacePage forcedType="skills" />);
195
+
196
+ expect(screen.getByRole("button", { name: "Back" })).toBeTruthy();
197
+ expect(screen.getByText("Code Review")).toBeTruthy();
198
+ expect(screen.queryByText("Calendar Sync")).toBeNull();
199
+ expect(screen.queryByText("Skill Catalog")).toBeNull();
200
+ expect(screen.queryByPlaceholderText("Search skills...")).toBeNull();
201
+
202
+ await user.click(screen.getByRole("button", { name: "Back" }));
203
+
204
+ expect(mocks.navigate).toHaveBeenCalledWith("/skills");
205
+ });
206
+
207
+ it("shows a scene loading state instead of stale catalog items", () => {
208
+ mocks.routeParams = { scene: "development-debugging" };
209
+ mocks.itemsQuery = {
210
+ ...createItemsQuery([]),
211
+ data: undefined,
212
+ isLoading: true,
213
+ isFetching: true,
214
+ };
215
+
216
+ render(<MarketplacePage forcedType="skills" />);
217
+
218
+ expect(screen.getByTestId("marketplace-scene-skeleton")).toBeTruthy();
219
+ expect(screen.queryByText("Calendar Sync")).toBeNull();
220
+ expect(screen.queryByPlaceholderText("Search skills...")).toBeNull();
221
+ });
222
+
223
+ it("keeps infinite loading available inside scene routes", () => {
224
+ mocks.routeParams = { scene: "development-debugging" };
225
+ mocks.itemsQuery = {
226
+ ...createItemsQuery(createSceneItems().slice(0, 2)),
227
+ hasNextPage: true,
228
+ isFetchingNextPage: true,
229
+ };
230
+
231
+ render(<MarketplacePage forcedType="skills" />);
232
+
233
+ expect(screen.getByTestId("marketplace-loading-more")).toBeTruthy();
234
+ });
235
+ });
@@ -0,0 +1,162 @@
1
+ import {
2
+ BriefcaseBusiness,
3
+ Cpu,
4
+ Globe2,
5
+ MessagesSquare,
6
+ PenLine,
7
+ Sparkles,
8
+ TerminalSquare,
9
+ } from "lucide-react";
10
+ import type { ComponentType } from "react";
11
+
12
+ export type MarketplaceShelfLocalizedText = {
13
+ zh: string;
14
+ en: string;
15
+ };
16
+
17
+ export type MarketplaceShelfSceneVisual = {
18
+ scene: string;
19
+ title?: MarketplaceShelfLocalizedText;
20
+ summary?: MarketplaceShelfLocalizedText;
21
+ icon: ComponentType<{ className?: string }>;
22
+ tone: keyof typeof MARKETPLACE_SHELF_TONE_STYLES;
23
+ span: string;
24
+ };
25
+
26
+ export const MARKETPLACE_SHELF_SCENE_VISUALS: MarketplaceShelfSceneVisual[] = [
27
+ {
28
+ scene: "development-debugging",
29
+ title: { zh: "开发与调试", en: "Development" },
30
+ summary: {
31
+ zh: "代码审查、错误定位、架构分析与交付验证。",
32
+ en: "Review, debug, analyze, and verify delivery work.",
33
+ },
34
+ icon: TerminalSquare,
35
+ tone: "dark",
36
+ span: "md:col-span-2",
37
+ },
38
+ {
39
+ scene: "office-collaboration",
40
+ title: { zh: "办公协作", en: "Office Work" },
41
+ summary: {
42
+ zh: "连接文档、日历、会议、邮件和团队通信。",
43
+ en: "Connect docs, calendars, meetings, mail, and teams.",
44
+ },
45
+ icon: BriefcaseBusiness,
46
+ tone: "green",
47
+ span: "md:col-span-2",
48
+ },
49
+ {
50
+ scene: "writing-content",
51
+ title: { zh: "写作与内容", en: "Writing" },
52
+ summary: {
53
+ zh: "把调研、写作、润色和发布组织成连续工作流。",
54
+ en: "Turn research, writing, polishing, and publishing into one flow.",
55
+ },
56
+ icon: PenLine,
57
+ tone: "amber",
58
+ span: "md:col-span-2",
59
+ },
60
+ {
61
+ scene: "browser-automation",
62
+ title: { zh: "浏览器自动化", en: "Browser" },
63
+ summary: {
64
+ zh: "操作网页、捕获动态内容并验证真实用户路径。",
65
+ en: "Operate pages, capture dynamic content, and verify user paths.",
66
+ },
67
+ icon: Globe2,
68
+ tone: "blue",
69
+ span: "md:col-span-2",
70
+ },
71
+ {
72
+ scene: "local-environment",
73
+ title: { zh: "本地环境", en: "Local" },
74
+ summary: {
75
+ zh: "管理终端、文件、运行时和本地服务。",
76
+ en: "Manage shells, files, runtimes, and local services.",
77
+ },
78
+ icon: Cpu,
79
+ tone: "violet",
80
+ span: "md:col-span-1",
81
+ },
82
+ {
83
+ scene: "social-platforms",
84
+ title: { zh: "社交平台", en: "Social" },
85
+ summary: {
86
+ zh: "处理发布、互动、检索和内容分发。",
87
+ en: "Handle posting, interaction, search, and distribution.",
88
+ },
89
+ icon: MessagesSquare,
90
+ tone: "sky",
91
+ span: "md:col-span-1",
92
+ },
93
+ {
94
+ scene: "nextclaw-official",
95
+ title: { zh: "NextClaw 官方", en: "NextClaw Official" },
96
+ summary: {
97
+ zh: "优先查看由 NextClaw 维护的原生能力。",
98
+ en: "Browse native capabilities maintained by NextClaw.",
99
+ },
100
+ icon: Sparkles,
101
+ tone: "teal",
102
+ span: "md:col-span-2",
103
+ },
104
+ ];
105
+
106
+ export const MARKETPLACE_SHELF_FALLBACK_VISUAL: Omit<MarketplaceShelfSceneVisual, "scene"> = {
107
+ icon: Sparkles,
108
+ tone: "dark",
109
+ span: "md:col-span-2",
110
+ };
111
+
112
+ export const MARKETPLACE_SHELF_TONE_STYLES = {
113
+ dark: {
114
+ card: "border-gray-200/80 bg-white text-gray-950",
115
+ icon: "border-stone-600 bg-stone-600 text-white",
116
+ title: "text-gray-950",
117
+ text: "text-gray-500",
118
+ meta: "text-gray-400",
119
+ },
120
+ blue: {
121
+ card: "border-gray-200/80 bg-white text-gray-950",
122
+ icon: "border-cyan-600 bg-cyan-600 text-white",
123
+ title: "text-gray-950",
124
+ text: "text-gray-500",
125
+ meta: "text-gray-400",
126
+ },
127
+ green: {
128
+ card: "border-gray-200/80 bg-white text-gray-950",
129
+ icon: "border-emerald-600 bg-emerald-600 text-white",
130
+ title: "text-gray-950",
131
+ text: "text-gray-500",
132
+ meta: "text-gray-400",
133
+ },
134
+ amber: {
135
+ card: "border-gray-200/80 bg-white text-gray-950",
136
+ icon: "border-amber-600 bg-amber-600 text-white",
137
+ title: "text-gray-950",
138
+ text: "text-gray-500",
139
+ meta: "text-gray-400",
140
+ },
141
+ violet: {
142
+ card: "border-gray-200/80 bg-white text-gray-950",
143
+ icon: "border-violet-500 bg-violet-500 text-white",
144
+ title: "text-gray-950",
145
+ text: "text-gray-500",
146
+ meta: "text-gray-400",
147
+ },
148
+ sky: {
149
+ card: "border-gray-200/80 bg-white text-gray-950",
150
+ icon: "border-sky-500 bg-sky-500 text-white",
151
+ title: "text-gray-950",
152
+ text: "text-gray-500",
153
+ meta: "text-gray-400",
154
+ },
155
+ teal: {
156
+ card: "border-gray-200/80 bg-white text-gray-950",
157
+ icon: "border-teal-600 bg-teal-600 text-white",
158
+ title: "text-gray-950",
159
+ text: "text-gray-500",
160
+ meta: "text-gray-400",
161
+ },
162
+ } as const;