@nextclaw/ui 0.15.2 → 0.15.4

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 (212) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/aiden0z-pptx-renderer.es-Bd99vut2.js +94 -0
  3. package/dist/assets/api-BOXOrpcJ.js +13 -0
  4. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +106 -0
  5. package/dist/assets/app-presenter-provider-D3BuuOyZ.css +1 -0
  6. package/dist/assets/appearance-settings-page-fLIJbx1C.js +1 -0
  7. package/dist/assets/{book-open-Ba2f7YiG.js → book-open-DWUjS6aW.js} +1 -1
  8. package/dist/assets/channels-list-page-CvCTJraa.js +8 -0
  9. package/dist/assets/chat-page-B42UUIFR.js +1 -0
  10. package/dist/assets/chunk-DseTPa7n.js +1 -0
  11. package/dist/assets/config-split-page-DBgt9S8b.js +1 -0
  12. package/dist/assets/confirm-dialog-GSXIvoRU.js +5 -0
  13. package/dist/assets/desktop-update-config-7bmNeWIC.js +1 -0
  14. package/dist/assets/dist-DC7f4TJH.js +41 -0
  15. package/dist/assets/doc-browser-CgdoLUT92.js +1 -0
  16. package/dist/assets/doc-browser-D0monHjG.js +1 -0
  17. package/dist/assets/doc-browser-context-CGwEOhBy.js +1 -0
  18. package/dist/assets/docx-preview-eFH9DRsS.js +20 -0
  19. package/dist/assets/download-DiP9zx-J.js +1 -0
  20. package/dist/assets/{external-link-DxuPaDoK.js → external-link-C6ZeSPef.js} +1 -1
  21. package/dist/assets/eye-suvKmtwn.js +1 -0
  22. package/dist/assets/folder-CMELbQGG.js +1 -0
  23. package/dist/assets/{form-actions-BztL7P_T.js → form-actions-CUAzpNe_.js} +1 -1
  24. package/dist/assets/index-BFeNDQ4R.css +1 -0
  25. package/dist/assets/index-D_6U-M94.js +2 -0
  26. package/dist/assets/jszip.min-Das2IAnb.js +2 -0
  27. package/dist/assets/loader-circle-tYHStefV.js +1 -0
  28. package/dist/assets/logo-badge-Cu5UGH_4.js +1 -0
  29. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +9 -0
  30. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +1 -0
  31. package/dist/assets/model-config-page-CWP6pyBm.js +1 -0
  32. package/dist/assets/plus-tywizkeY.js +1 -0
  33. package/dist/assets/popover-DImguRok.js +1 -0
  34. package/dist/assets/provider-models-C_yOh6DE.js +1 -0
  35. package/dist/assets/provider-scoped-model-input-CE7p4SbJ.js +1 -0
  36. package/dist/assets/providers-config-page-BKPo8SjH.js +1 -0
  37. package/dist/assets/react-5sjAT_6d.js +8 -0
  38. package/dist/assets/{refresh-cw-eT8BF-8B.js → refresh-cw-CHLBzWWj.js} +1 -1
  39. package/dist/assets/remote-DXJryUxB.js +1 -0
  40. package/dist/assets/rotate-cw-BefO5brU.js +1 -0
  41. package/dist/assets/runtime-config-page-1Axv4QGu.js +1 -0
  42. package/dist/assets/{save-Cb2g0BOi.js → save-BABzyrjB.js} +1 -1
  43. package/dist/assets/{search-BcHjwdsb.js → search-BTqxG3ZG.js} +1 -1
  44. package/dist/assets/search-config-page-BkKKmxmS.js +1 -0
  45. package/dist/assets/secrets-config-page-CWJMyu-k.js +3 -0
  46. package/dist/assets/security-config-DeQUIXfi.js +1 -0
  47. package/dist/assets/select-TYZDBNmq.js +1 -0
  48. package/dist/assets/setting-row-DR23Z_5I.js +1 -0
  49. package/dist/assets/{settings-2-2DWauOjY.js → settings-2-D8WWcBnw.js} +1 -1
  50. package/dist/assets/{skeleton-P5oM14E1.js → skeleton-CloHeg0S.js} +1 -1
  51. package/dist/assets/tabs-CX-ckWoo.js +1 -0
  52. package/dist/assets/tag-chip-mCc23Rhb.js +1 -0
  53. package/dist/assets/trash-2-SAW1Z4Po.js +1 -0
  54. package/dist/assets/use-config-BucdaK23.js +1 -0
  55. package/dist/assets/use-confirm-dialog-C95F6sT1.js +1 -0
  56. package/dist/assets/use-viewport-layout-C6m3Nte7.js +1 -0
  57. package/dist/assets/x-DtCPHwCu.js +1 -0
  58. package/dist/assets/xlsx-B6zVt1RK.js +64 -0
  59. package/dist/index.html +34 -28
  60. package/package.json +14 -9
  61. package/src/app/components/layout/sidebar-items.tsx +3 -3
  62. package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
  63. package/src/app/components/layout/sidebar.tsx +1 -1
  64. package/src/app/styles/design-system.css +17 -4
  65. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +41 -0
  66. package/src/features/chat/components/chat-popover-content.tsx +26 -0
  67. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
  68. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
  69. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
  70. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
  71. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
  72. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
  73. package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
  74. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  75. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
  76. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +4 -15
  77. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
  78. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +69 -54
  79. package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
  80. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +62 -1
  81. package/src/features/chat/features/message/components/chat-message-list.container.tsx +63 -2
  82. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
  83. package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +11 -1
  84. package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +23 -0
  85. package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +5 -0
  86. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +138 -0
  87. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +37 -0
  88. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +5 -4
  89. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
  90. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
  91. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
  92. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  93. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +5 -5
  94. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +4 -4
  95. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +4 -4
  96. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +463 -99
  97. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
  98. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
  99. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
  100. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +11 -5
  101. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +8 -6
  102. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +99 -67
  103. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
  104. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +56 -6
  105. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +29 -7
  106. package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
  107. package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
  108. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
  109. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
  110. package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
  111. package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
  112. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
  113. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
  114. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +53 -10
  115. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +68 -2
  116. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +21 -0
  117. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +63 -0
  118. package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
  119. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
  120. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +120 -38
  121. package/src/features/chat/index.ts +1 -0
  122. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +76 -0
  123. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +44 -60
  124. package/src/features/chat/managers/chat-thread.manager.ts +123 -124
  125. package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
  126. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +78 -1
  127. package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
  128. package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
  129. package/src/features/chat/stores/chat-thread.store.ts +73 -13
  130. package/src/features/cron/components/cron-config.tsx +63 -80
  131. package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +44 -6
  132. package/src/features/marketplace/components/marketplace-detail-doc.tsx +8 -4
  133. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  134. package/src/features/marketplace/hooks/use-marketplace-detail-doc-entry.ts +84 -0
  135. package/src/features/panel-apps/components/panel-apps-list.tsx +2 -2
  136. package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
  137. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
  138. package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
  139. package/src/features/side-dock/components/side-dock.tsx +3 -3
  140. package/src/index.css +275 -15
  141. package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
  142. package/src/shared/components/actions/action-link.tsx +1 -1
  143. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
  144. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
  145. package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
  146. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
  147. package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
  148. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
  149. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
  150. package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
  151. package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
  152. package/src/shared/components/file-type-icon.tsx +272 -0
  153. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
  154. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
  155. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  156. package/src/shared/components/ui/button.tsx +1 -1
  157. package/src/shared/components/ui/dialog.tsx +1 -1
  158. package/src/shared/components/ui/input.tsx +1 -1
  159. package/src/shared/components/ui/select.tsx +1 -1
  160. package/src/shared/components/ui/switch.tsx +1 -1
  161. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
  162. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
  163. package/src/shared/components/ui/tabs.tsx +1 -1
  164. package/src/shared/components/ui/textarea.tsx +1 -1
  165. package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
  166. package/src/shared/lib/api/utils/server-path.utils.ts +19 -20
  167. package/src/shared/lib/i18n/locales/en-US/chat.json +99 -0
  168. package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
  169. package/src/shared/lib/i18n/locales/zh-CN/chat.json +99 -0
  170. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
  171. package/dist/assets/api-B7Qigvtm.js +0 -13
  172. package/dist/assets/appearance-settings-page-CuE0Iiqj.js +0 -1
  173. package/dist/assets/channels-list-page-CTsM6TcP.js +0 -8
  174. package/dist/assets/chat-page-C_5dcuE3.js +0 -1
  175. package/dist/assets/config-split-page-Fbb4PtTf.js +0 -1
  176. package/dist/assets/confirm-dialog-D2EG3nqE.js +0 -5
  177. package/dist/assets/desktop-update-config-BGR3HUi1.js +0 -1
  178. package/dist/assets/dist-7JTjycxE.js +0 -1
  179. package/dist/assets/dist-wXLhk4hl.js +0 -1
  180. package/dist/assets/doc-browser-bHyXnxlj.js +0 -1
  181. package/dist/assets/doc-browser-context-CYAGm5d5.js +0 -1
  182. package/dist/assets/doc-browser-gTrQ3jCF.js +0 -1
  183. package/dist/assets/es2015-j1SRCIX5.js +0 -41
  184. package/dist/assets/i18n-provider-B4oMxOFx.js +0 -1
  185. package/dist/assets/index-CvrvblgX.css +0 -1
  186. package/dist/assets/index-Obd4JYU2.js +0 -104
  187. package/dist/assets/loader-circle-CdzgK4eH.js +0 -1
  188. package/dist/assets/mcp-marketplace-page-Bt7CuOoq.js +0 -9
  189. package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +0 -1
  190. package/dist/assets/model-config-page-DeKk9gvE.js +0 -1
  191. package/dist/assets/plus-Bx7wQbhG.js +0 -1
  192. package/dist/assets/provider-scoped-model-input-DsH2LF1q.js +0 -1
  193. package/dist/assets/providers-config-page-BFnGp68F.js +0 -1
  194. package/dist/assets/react-UirzUuST.js +0 -8
  195. package/dist/assets/remote-CDkHZtA8.js +0 -1
  196. package/dist/assets/rotate-cw-1JqxHTuw.js +0 -1
  197. package/dist/assets/runtime-config-page-BelAqiNG.js +0 -1
  198. package/dist/assets/search-config-page-zPm6--Ig.js +0 -1
  199. package/dist/assets/secrets-config-page-Cxyp-KuQ.js +0 -3
  200. package/dist/assets/security-config-B_gCq9fn.js +0 -1
  201. package/dist/assets/select-CdSEOwVM.js +0 -1
  202. package/dist/assets/setting-row-CTAtGexr.js +0 -1
  203. package/dist/assets/side-dock-DPvakJ0a.js +0 -3
  204. package/dist/assets/switch-BU_UEULb.js +0 -1
  205. package/dist/assets/tag-chip-D-WsKoT1.js +0 -1
  206. package/dist/assets/tooltip-CjkOaHW7.js +0 -1
  207. package/dist/assets/use-config--hx5oxQn.js +0 -1
  208. package/dist/assets/use-confirm-dialog-CD9a_SK1.js +0 -1
  209. package/dist/assets/x-DT5x45JU.js +0 -1
  210. /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-fDrYfl0l.js} +0 -0
  211. /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-zZvY9U63.js} +0 -0
  212. /package/dist/assets/{middleware-BILWbJE4.js → middleware-BLMHv9YF.js} +0 -0
@@ -8,9 +8,9 @@ import type {
8
8
  import { Skeleton } from '@/shared/components/ui/skeleton';
9
9
  import { t } from '@/shared/lib/i18n';
10
10
  import { cn } from '@/shared/lib/utils';
11
+ import { useMarketplaceDetailDocEntry } from '@/features/marketplace/hooks/use-marketplace-detail-doc-entry';
11
12
  import {
12
13
  setMarketplaceDetailDocEntry,
13
- useMarketplaceDetailDocStore,
14
14
  type MarketplaceDetailDocEntry,
15
15
  } from '@/features/marketplace/stores/marketplace-detail-doc.store';
16
16
 
@@ -422,13 +422,17 @@ function MarketplaceDetailDocContent({ entry }: { entry: MarketplaceDetailDocEnt
422
422
 
423
423
  function MarketplaceDetailDoc({ currentUrl }: { currentUrl: string }) {
424
424
  const detailId = readMarketplaceDetailDocId(currentUrl);
425
- const entry = useMarketplaceDetailDocStore((state) => (detailId ? state.entries[detailId] : undefined));
426
- if (!detailId || !entry) {
425
+ const entry = useMarketplaceDetailDocEntry(detailId);
426
+
427
+ if (!detailId) {
427
428
  return <MarketplaceDetailUnavailable />;
428
429
  }
429
- if (entry.status === 'loading') {
430
+ if (!entry || entry.status === 'loading') {
430
431
  return <MarketplaceDetailSkeleton />;
431
432
  }
433
+ if (entry.status === 'error' && !entry.contentRaw) {
434
+ return <MarketplaceDetailUnavailable />;
435
+ }
432
436
  return <MarketplaceDetailDocContent entry={entry} />;
433
437
  }
434
438
 
@@ -37,7 +37,7 @@ export function FilterPanel({
37
37
  value={searchText}
38
38
  onChange={(event) => onSearchTextChange(event.target.value)}
39
39
  placeholder={searchPlaceholder}
40
- className="h-9 w-full rounded-xl border border-gray-200/80 pl-9 pr-9 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40"
40
+ className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0 focus:border-border"
41
41
  />
42
42
  {isRefreshing && (
43
43
  <Loader2
@@ -0,0 +1,84 @@
1
+ import { useEffect } from 'react';
2
+ import { fetchMarketplaceSkillContent } from '@/shared/lib/api';
3
+ import { t } from '@/shared/lib/i18n';
4
+ import {
5
+ setMarketplaceDetailDocEntry,
6
+ useMarketplaceDetailDocStore,
7
+ } from '@/features/marketplace/stores/marketplace-detail-doc.store';
8
+
9
+ function parseMarketplaceDetailScopeKey(
10
+ detailId: string,
11
+ ): { scope: string; key: string } | null {
12
+ const separator = detailId.indexOf(':');
13
+ if (separator <= 0 || separator >= detailId.length - 1) {
14
+ return null;
15
+ }
16
+ return {
17
+ scope: detailId.slice(0, separator),
18
+ key: detailId.slice(separator + 1),
19
+ };
20
+ }
21
+
22
+ function isRehydratableSkillScope(scope: string): boolean {
23
+ return scope === 'skill' || scope === 'skill-preview';
24
+ }
25
+
26
+ export function useMarketplaceDetailDocEntry(detailId: string | null) {
27
+ const entry = useMarketplaceDetailDocStore((state) =>
28
+ detailId ? state.entries[detailId] : undefined,
29
+ );
30
+
31
+ useEffect(() => {
32
+ if (!detailId || useMarketplaceDetailDocStore.getState().entries[detailId]) {
33
+ return;
34
+ }
35
+ const parsed = parseMarketplaceDetailScopeKey(detailId);
36
+ if (!parsed || !isRehydratableSkillScope(parsed.scope)) {
37
+ return;
38
+ }
39
+
40
+ let cancelled = false;
41
+ const title = parsed.key;
42
+ const typeLabel = t('marketplaceTypeSkill');
43
+
44
+ void fetchMarketplaceSkillContent(parsed.key)
45
+ .then((content) => {
46
+ if (cancelled) {
47
+ return;
48
+ }
49
+ setMarketplaceDetailDocEntry({
50
+ id: detailId,
51
+ title,
52
+ typeLabel,
53
+ spec: parsed.key,
54
+ status: 'ready',
55
+ summary: t('marketplaceInstalledLocalSummary'),
56
+ metadataRaw: content.metadataRaw,
57
+ contentRaw: content.bodyRaw || content.raw,
58
+ sourceUrl: content.sourceUrl,
59
+ sourceLabel: content.source ? `Source (${content.source})` : undefined,
60
+ });
61
+ })
62
+ .catch(() => {
63
+ if (cancelled) {
64
+ return;
65
+ }
66
+ setMarketplaceDetailDocEntry({
67
+ id: detailId,
68
+ title,
69
+ typeLabel,
70
+ spec: parsed.key,
71
+ status: 'error',
72
+ summary: t('marketplaceInstalledLocalSummary'),
73
+ metadataRaw: JSON.stringify({ skill: parsed.key }, null, 2),
74
+ contentRaw: t('marketplaceDetailUnavailableDescription'),
75
+ });
76
+ });
77
+
78
+ return () => {
79
+ cancelled = true;
80
+ };
81
+ }, [detailId]);
82
+
83
+ return entry;
84
+ }
@@ -204,7 +204,7 @@ function PanelAppsEmptyGuide({
204
204
  <button
205
205
  type="button"
206
206
  onClick={onRefresh}
207
- className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
207
+ className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
208
208
  >
209
209
  <RefreshCw className="h-3.5 w-3.5" />
210
210
  {t('panelAppsRefresh')}
@@ -245,7 +245,7 @@ function PanelAppsEmptyGuideStep({
245
245
  <button
246
246
  type="button"
247
247
  onClick={onAction}
248
- className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
248
+ className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
249
249
  >
250
250
  {content}
251
251
  </button>
@@ -80,7 +80,7 @@ export function RightPanelResourceHomePage({ open }: RightPanelResourceHomePageP
80
80
  key={item.id}
81
81
  type="button"
82
82
  onClick={() => openTarget(item.target)}
83
- className="group flex min-h-[68px] flex-col items-center gap-1.5 rounded-md px-1.5 py-1.5 text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
83
+ className="group flex min-h-[68px] flex-col items-center gap-1.5 rounded-md px-1.5 py-1.5 text-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
84
84
  >
85
85
  <span className={cn('flex h-10 w-10 shrink-0 items-center justify-center rounded-md transition-colors', view.accentClassName, view.hoverAccentClassName)}>
86
86
  {view.icon}
@@ -1,25 +1,42 @@
1
- import { fireEvent, render, screen } from '@testing-library/react';
2
- import { beforeEach, describe, expect, it } from 'vitest';
3
- import { AppearanceSettingsPage } from '@/features/settings/pages/appearance-settings-page';
4
- import { useSideDockStore } from '@/features/side-dock';
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { beforeEach, describe, expect, it } from "vitest";
3
+ import { AppearanceSettingsPage } from "@/features/settings/pages/appearance-settings-page";
4
+ import { useChatMessageLayoutStore } from "@/features/chat";
5
+ import { useSideDockStore } from "@/features/side-dock";
5
6
 
6
- describe('AppearanceSettingsPage', () => {
7
+ describe("AppearanceSettingsPage", () => {
7
8
  beforeEach(() => {
8
9
  window.localStorage.clear();
10
+ useChatMessageLayoutStore.getState().setLayout("card");
9
11
  useSideDockStore.getState().setVisible(true);
10
12
  useSideDockStore.getState().setPinnedItems([]);
11
13
  });
12
14
 
13
- it('reopens the SideDock from the appearance settings switch', () => {
15
+ it("reopens the SideDock from the appearance settings switch", () => {
14
16
  useSideDockStore.getState().setVisible(false);
15
17
 
16
18
  render(<AppearanceSettingsPage />);
17
19
 
18
- const visibilitySwitch = screen.getByRole('switch', { name: 'Show SideDock' });
19
- expect(visibilitySwitch.getAttribute('aria-checked')).toBe('false');
20
+ const visibilitySwitch = screen.getByRole("switch", {
21
+ name: "Show SideDock",
22
+ });
23
+ expect(visibilitySwitch.getAttribute("aria-checked")).toBe("false");
20
24
 
21
25
  fireEvent.click(visibilitySwitch);
22
26
 
23
27
  expect(useSideDockStore.getState().isVisible).toBe(true);
24
28
  });
29
+
30
+ it("switches the chat message layout from appearance settings", () => {
31
+ render(<AppearanceSettingsPage />);
32
+
33
+ const cardOption = screen.getByRole("radio", { name: /^Cards/ });
34
+ const flatOption = screen.getByRole("radio", { name: /^Flat/ });
35
+ expect(cardOption.getAttribute("aria-checked")).toBe("true");
36
+
37
+ fireEvent.click(flatOption);
38
+
39
+ expect(flatOption.getAttribute("aria-checked")).toBe("true");
40
+ expect(useChatMessageLayoutStore.getState().layout).toBe("flat");
41
+ });
25
42
  });
@@ -1,33 +1,112 @@
1
- import { Palette } from 'lucide-react';
2
- import { PageHeader, PageLayout } from '@/app/components/layout/page-layout';
3
- import { SettingRow } from '@/shared/components/settings/setting-row';
4
- import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card';
5
- import { Switch } from '@/shared/components/ui/switch';
6
- import { useSideDockStore } from '@/features/side-dock';
7
- import { t } from '@/shared/lib/i18n';
1
+ import type { ChatMessageLayout } from "@nextclaw/agent-chat-ui";
2
+ import { Check, Palette } from "lucide-react";
3
+ import { PageHeader, PageLayout } from "@/app/components/layout/page-layout";
4
+ import { useChatMessageLayoutStore } from "@/features/chat";
5
+ import { SettingRow } from "@/shared/components/settings/setting-row";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@/shared/components/ui/card";
12
+ import { Switch } from "@/shared/components/ui/switch";
13
+ import { useSideDockStore } from "@/features/side-dock";
14
+ import { t } from "@/shared/lib/i18n";
15
+ import { cn } from "@/shared/lib/utils";
16
+
17
+ const CHAT_MESSAGE_LAYOUT_OPTIONS: Array<{
18
+ value: ChatMessageLayout;
19
+ labelKey: string;
20
+ descriptionKey: string;
21
+ }> = [
22
+ {
23
+ value: "card",
24
+ labelKey: "chatMessageLayoutCard",
25
+ descriptionKey: "chatMessageLayoutCardDescription",
26
+ },
27
+ {
28
+ value: "flat",
29
+ labelKey: "chatMessageLayoutFlat",
30
+ descriptionKey: "chatMessageLayoutFlatDescription",
31
+ },
32
+ ];
8
33
 
9
34
  export function AppearanceSettingsPage() {
35
+ const messageLayout = useChatMessageLayoutStore((state) => state.layout);
36
+ const setMessageLayout = useChatMessageLayoutStore(
37
+ (state) => state.setLayout,
38
+ );
10
39
  const isSideDockVisible = useSideDockStore((state) => state.isVisible);
11
40
  const setSideDockVisible = useSideDockStore((state) => state.setVisible);
12
41
 
13
42
  return (
14
43
  <PageLayout>
15
- <PageHeader title={t('appearance')} />
44
+ <PageHeader title={t("appearance")} />
16
45
  <Card>
17
46
  <CardHeader>
18
47
  <CardTitle className="flex items-center gap-2">
19
48
  <Palette className="h-4 w-4" />
20
- {t('appearance')}
49
+ {t("appearance")}
21
50
  </CardTitle>
22
51
  </CardHeader>
23
- <CardContent>
52
+ <CardContent className="space-y-4">
53
+ <SettingRow
54
+ title={t("chatMessageLayoutTitle")}
55
+ description={t("chatMessageLayoutDescription")}
56
+ >
57
+ <div
58
+ role="radiogroup"
59
+ aria-label={t("chatMessageLayoutTitle")}
60
+ className="grid gap-2 sm:grid-cols-2"
61
+ >
62
+ {CHAT_MESSAGE_LAYOUT_OPTIONS.map((option) => {
63
+ const selected = option.value === messageLayout;
64
+ return (
65
+ <button
66
+ key={option.value}
67
+ type="button"
68
+ role="radio"
69
+ aria-checked={selected}
70
+ onClick={() => setMessageLayout(option.value)}
71
+ className={cn(
72
+ "flex min-w-0 items-start gap-3 rounded-xl border px-3 py-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
73
+ selected
74
+ ? "border-primary/35 bg-primary/5 text-foreground"
75
+ : "border-border bg-card text-muted-foreground hover:border-border/80 hover:bg-muted/45 hover:text-foreground",
76
+ )}
77
+ >
78
+ <span
79
+ className={cn(
80
+ "mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full border",
81
+ selected
82
+ ? "border-primary bg-primary text-primary-foreground"
83
+ : "border-border bg-background",
84
+ )}
85
+ >
86
+ {selected ? (
87
+ <Check className="h-2.5 w-2.5" strokeWidth={3} />
88
+ ) : null}
89
+ </span>
90
+ <span className="min-w-0">
91
+ <span className="block text-sm font-medium">
92
+ {t(option.labelKey)}
93
+ </span>
94
+ <span className="mt-1 block text-xs leading-5 text-muted-foreground">
95
+ {t(option.descriptionKey)}
96
+ </span>
97
+ </span>
98
+ </button>
99
+ );
100
+ })}
101
+ </div>
102
+ </SettingRow>
24
103
  <SettingRow
25
- title={t('sideDockVisibilityTitle')}
26
- description={t('sideDockVisibilityDescription')}
104
+ title={t("sideDockVisibilityTitle")}
105
+ description={t("sideDockVisibilityDescription")}
27
106
  control={
28
107
  <Switch
29
108
  id="appearance-side-dock-visible"
30
- aria-label={t('sideDockVisibilityTitle')}
109
+ aria-label={t("sideDockVisibilityTitle")}
31
110
  checked={isSideDockVisible}
32
111
  onCheckedChange={setSideDockVisible}
33
112
  />
@@ -95,7 +95,7 @@ function SideDockButton({
95
95
  onClick={() => onOpen(item)}
96
96
  className={cn(
97
97
  'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
98
- 'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
98
+ 'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
99
99
  active
100
100
  ? 'bg-primary/10 text-primary'
101
101
  : 'bg-transparent',
@@ -113,7 +113,7 @@ function SideDockButton({
113
113
  event.stopPropagation();
114
114
  onUnpin(item);
115
115
  }}
116
- className="absolute -right-1 -top-1 hidden h-4 w-4 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm ring-1 ring-border/70 transition-colors hover:bg-muted hover:text-foreground group-hover:flex focus-visible:flex focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
116
+ className="absolute -right-1 -top-1 hidden h-4 w-4 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm ring-1 ring-border/70 transition-colors hover:bg-muted hover:text-foreground group-hover:flex focus-visible:flex focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
117
117
  >
118
118
  <X className="h-3 w-3" aria-hidden="true" />
119
119
  </button>
@@ -144,7 +144,7 @@ function SideDockActionButton({
144
144
  onClick={onClick}
145
145
  className={cn(
146
146
  'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
147
- 'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
147
+ 'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
148
148
  )}
149
149
  >
150
150
  <Icon className="h-5 w-5" aria-hidden="true" />