@nextclaw/ui 0.14.0 → 0.14.1-beta.1

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 (194) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-B50yVdT8.js → api-dQ6uQSZG.js} +3 -3
  3. package/dist/assets/channels-list-page-DfpjB8XB.js +8 -0
  4. package/dist/assets/chat-page-D3QaEqjs.js +105 -0
  5. package/dist/assets/config-split-page-CeFfbaqc.js +1 -0
  6. package/dist/assets/{confirm-dialog-DRSelLdI.js → confirm-dialog-C95Y4QPC.js} +1 -1
  7. package/dist/assets/desktop-update-config-CcK6jiEb.js +1 -0
  8. package/dist/assets/{dist-D19wiQwB.js → dist-DTGzeil6.js} +1 -1
  9. package/dist/assets/{dist-NZRV-BxD.js → dist-fGogBmPA.js} +1 -1
  10. package/dist/assets/doc-browser-CuBTcBEZ.js +1 -0
  11. package/dist/assets/doc-browser-DVHhEb96.js +1 -0
  12. package/dist/assets/{doc-browser-context-C8KPbj-p.js → doc-browser-context-BfMXwnrz.js} +1 -1
  13. package/dist/assets/{ellipsis-BYy8TP0A.js → ellipsis-D2F4KMiU.js} +1 -1
  14. package/dist/assets/{external-link-528k6MDD.js → external-link-CcgtBK26.js} +1 -1
  15. package/dist/assets/index-ByJCbQqr.css +1 -0
  16. package/dist/assets/index-DjXqiloY.js +103 -0
  17. package/dist/assets/mcp-marketplace-page-98-V__3u.js +1 -0
  18. package/dist/assets/mcp-marketplace-page-BNq_PNmO.js +40 -0
  19. package/dist/assets/model-config-page-aAUkZkSX.js +1 -0
  20. package/dist/assets/plus-1Rxl5FlV.js +1 -0
  21. package/dist/assets/provider-scoped-model-input-we4_Z4gP.js +1 -0
  22. package/dist/assets/providers-config-page-CtMkPSyu.js +1 -0
  23. package/dist/assets/{react-Dsotw5vM.js → react-CTqdhFMk.js} +1 -1
  24. package/dist/assets/remote-fQ7N_r8u.js +1 -0
  25. package/dist/assets/rotate-cw-CzfIeAJj.js +1 -0
  26. package/dist/assets/runtime-config-page-TD1UrTbG.js +1 -0
  27. package/dist/assets/{save-CHDnDTok.js → save-C9fKWfVB.js} +1 -1
  28. package/dist/assets/search-D5FQ5q0X.js +1 -0
  29. package/dist/assets/search-config-page-AMEJJNIS.js +1 -0
  30. package/dist/assets/secrets-config-page-CcgJnvBO.js +3 -0
  31. package/dist/assets/select-Cu5n0E-3.js +41 -0
  32. package/dist/assets/{tag-chip-Cr8GwpRE.js → tag-chip-C5aOtjxu.js} +1 -1
  33. package/dist/assets/use-config-D8TESeo6.js +1 -0
  34. package/dist/assets/x-1qETdQ70.js +1 -0
  35. package/dist/index.html +14 -27
  36. package/package.json +9 -9
  37. package/src/app/presenters/app.presenter.ts +2 -0
  38. package/src/features/account/components/account-panel.tsx +2 -2
  39. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +12 -63
  40. package/src/features/chat/components/conversation/chat-conversation-content.tsx +11 -50
  41. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +1 -0
  42. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +15 -2
  43. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +122 -85
  44. package/src/features/chat/features/input/hooks/use-chat-input-bar-query-state.ts +120 -0
  45. package/src/features/chat/features/input/hooks/use-chat-model-favorites.ts +87 -0
  46. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +66 -41
  47. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +1 -0
  48. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +22 -36
  49. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +80 -52
  50. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +89 -0
  51. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +9 -1
  52. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +115 -0
  53. package/src/features/chat/features/message/components/chat-message-list.container.tsx +4 -0
  54. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +310 -0
  55. package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +119 -1
  56. package/src/features/chat/features/message/utils/__tests__/chat-message-test.utils.ts +43 -0
  57. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +1 -346
  58. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +51 -0
  59. package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +28 -5
  60. package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +14 -9
  61. package/src/features/chat/features/message/utils/file-operation/diff.utils.ts +39 -36
  62. package/src/features/chat/features/message/utils/file-operation/line-builder.utils.ts +46 -38
  63. package/src/features/chat/features/message/utils/file-operation/parsed-block.utils.ts +18 -15
  64. package/src/features/chat/features/ncp/hooks/__tests__/use-ui-show-content-event.test.tsx +1 -0
  65. package/src/features/chat/features/ncp/utils/ncp-chat-query-derived.utils.ts +11 -3
  66. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +27 -0
  67. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +25 -25
  68. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +19 -1
  69. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +46 -0
  70. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +15 -2
  71. package/src/features/chat/features/session/hooks/__tests__/use-selected-session-context-window-indicator.test.tsx +63 -0
  72. package/src/features/chat/features/session/hooks/use-selected-session-context-window-indicator.ts +7 -10
  73. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +8 -0
  74. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -0
  75. package/src/features/chat/features/session-type/components/__tests__/chat-sidebar-create-menu.test.tsx +24 -0
  76. package/src/features/chat/features/session-type/components/chat-sidebar-create-menu.tsx +10 -1
  77. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +1 -0
  78. package/src/features/chat/features/session-type/utils/chat-session-type.utils.ts +4 -0
  79. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +193 -0
  80. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +123 -0
  81. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +104 -0
  82. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +72 -0
  83. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +54 -0
  84. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +132 -0
  85. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +82 -0
  86. package/src/features/chat/features/welcome/components/chat-welcome.tsx +121 -0
  87. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +53 -0
  88. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-selection.utils.test.ts +63 -0
  89. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-visibility.utils.test.ts +34 -0
  90. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +71 -0
  91. package/src/features/chat/features/welcome/utils/chat-welcome-selection.utils.ts +79 -0
  92. package/src/features/chat/features/welcome/utils/chat-welcome-visibility.utils.ts +17 -0
  93. package/src/features/chat/index.ts +1 -0
  94. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +93 -0
  95. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +129 -0
  96. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +34 -0
  97. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +143 -1
  98. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +30 -0
  99. package/src/features/chat/managers/__tests__/recent-selection.manager.test.ts +37 -14
  100. package/src/features/chat/managers/chat-draft-intent.manager.ts +46 -0
  101. package/src/features/chat/managers/chat-input.manager.ts +37 -15
  102. package/src/features/chat/managers/chat-run.manager.ts +3 -0
  103. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +19 -2
  104. package/src/features/chat/managers/chat-thread.manager.ts +4 -0
  105. package/src/features/chat/managers/chat-ui.manager.ts +2 -6
  106. package/src/features/chat/managers/recent-selection.manager.ts +21 -67
  107. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +47 -1
  108. package/src/features/chat/pages/ncp-chat-page.tsx +18 -0
  109. package/src/features/chat/stores/chat-input.store.ts +9 -1
  110. package/src/features/chat/stores/recent-selection.store.ts +85 -0
  111. package/src/features/chat/types/chat-input-bar.types.ts +5 -1
  112. package/src/features/marketplace/utils/marketplace-installed-cache.utils.ts +27 -23
  113. package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +137 -0
  114. package/src/features/panel-apps/components/panel-apps-list.tsx +119 -5
  115. package/src/features/panel-apps/index.ts +2 -0
  116. package/src/features/panel-apps/utils/panel-app-entry-match.utils.ts +17 -0
  117. package/src/features/settings/components/config/provider-form.tsx +284 -387
  118. package/src/features/settings/components/config/provider-status-badge.tsx +4 -4
  119. package/src/features/settings/components/config/secrets-config-form.tsx +11 -135
  120. package/src/features/settings/hooks/use-provider-auth-flow.ts +140 -0
  121. package/src/features/settings/pages/__tests__/model-config-page.test.tsx +16 -0
  122. package/src/features/settings/pages/secrets-config-page.tsx +2 -1
  123. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +60 -1
  124. package/src/features/settings/utils/__tests__/secrets-config-form.utils.test.ts +66 -0
  125. package/src/features/settings/utils/provider-form-context.utils.ts +131 -0
  126. package/src/features/settings/utils/provider-form-model.utils.ts +101 -0
  127. package/src/features/settings/utils/provider-form-support.utils.ts +102 -1
  128. package/src/features/settings/utils/secrets-config-form.utils.ts +139 -0
  129. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +17 -11
  130. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +14 -9
  131. package/src/features/system-status/components/config/runtime-config-editor.tsx +8 -5
  132. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +14 -9
  133. package/src/features/system-status/components/config/runtime-settings-card.tsx +16 -7
  134. package/src/features/system-status/components/runtime-security-card.tsx +79 -25
  135. package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +34 -0
  136. package/src/shared/components/common/provider-scoped-model-input.tsx +1 -0
  137. package/src/shared/components/common/searchable-model-input.tsx +102 -87
  138. package/src/shared/components/ui/README.md +7 -0
  139. package/src/shared/components/ui/popover.tsx +19 -3
  140. package/src/shared/components/ui/select.tsx +14 -4
  141. package/src/shared/hooks/use-infinite-scroll-loader.ts +14 -10
  142. package/src/shared/lib/api/chat-session-type.types.ts +7 -0
  143. package/src/shared/lib/api/index.ts +2 -0
  144. package/src/shared/lib/api/ncp-session.types.ts +1 -0
  145. package/src/shared/lib/api/preferences/preference.types.ts +22 -0
  146. package/src/shared/lib/api/preferences/preference.utils.ts +40 -0
  147. package/src/shared/lib/api/types.ts +3 -0
  148. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -1
  149. package/src/shared/lib/i18n/locales/en-US/core.json +1 -0
  150. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +10 -0
  151. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -1
  152. package/src/shared/lib/i18n/locales/zh-CN/core.json +1 -0
  153. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +10 -0
  154. package/dist/assets/app-presenter-provider-BI-ewLbO.js +0 -3
  155. package/dist/assets/arrow-left-BOCJyLbQ.js +0 -1
  156. package/dist/assets/book-open-Bd2Xbrrc.js +0 -1
  157. package/dist/assets/channels-list-page-moJkpxtU.js +0 -8
  158. package/dist/assets/chat-page-CO27cl4v.js +0 -105
  159. package/dist/assets/config-split-page-CsQ2rIdq.js +0 -1
  160. package/dist/assets/desktop-update-config-qa-SynOc.js +0 -1
  161. package/dist/assets/doc-browser-2ddUJCA0.js +0 -1
  162. package/dist/assets/doc-browser-BjHUczQb.js +0 -1
  163. package/dist/assets/es2015-BUIIKCNh.js +0 -41
  164. package/dist/assets/host-capabilities-9bB_998Z.js +0 -1
  165. package/dist/assets/index-CMNrRhnG.js +0 -101
  166. package/dist/assets/index-UTmTW1eU.css +0 -1
  167. package/dist/assets/key-round-D3vD9dj7.js +0 -1
  168. package/dist/assets/loader-circle-DV9TSRns.js +0 -1
  169. package/dist/assets/mcp-marketplace-page-CS5Lh3oA.js +0 -1
  170. package/dist/assets/mcp-marketplace-page-DEOMcWb0.js +0 -40
  171. package/dist/assets/model-config-page-BSc4o7pb.js +0 -1
  172. package/dist/assets/notice-card-qqlbsOoR.js +0 -1
  173. package/dist/assets/plus-Bf9qhPDO.js +0 -1
  174. package/dist/assets/popover-BnmPOXe3.js +0 -1
  175. package/dist/assets/provider-scoped-model-input-Ca56s475.js +0 -1
  176. package/dist/assets/providers-config-page-D8Kf4U3u.js +0 -1
  177. package/dist/assets/refresh-cw-Bmx4wc9P.js +0 -1
  178. package/dist/assets/remote-DNMEy_c4.js +0 -1
  179. package/dist/assets/rotate-cw-CL0sVBel.js +0 -1
  180. package/dist/assets/runtime-config-page-CUFtKr0U.js +0 -1
  181. package/dist/assets/search-D7L-TCH-.js +0 -1
  182. package/dist/assets/search-config-page-CA3RVAef.js +0 -1
  183. package/dist/assets/secrets-config-page-95yaH1fa.js +0 -3
  184. package/dist/assets/status-dot-Dm4h-bjn.js +0 -1
  185. package/dist/assets/tabs-CJvw5Lqp.js +0 -1
  186. package/dist/assets/tooltip-Bxzi6aDL.js +0 -1
  187. package/dist/assets/trash-2-CSfX-BvU.js +0 -1
  188. package/dist/assets/use-config-TzwzTgg0.js +0 -1
  189. package/dist/assets/x-fkIWvi07.js +0 -1
  190. package/src/features/chat/components/__tests__/chat-welcome.test.tsx +0 -45
  191. package/src/features/chat/components/chat-welcome.tsx +0 -102
  192. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-bar-controller.test.tsx +0 -152
  193. package/src/features/chat/features/input/hooks/use-chat-input-bar-controller.ts +0 -142
  194. /package/dist/assets/{config-hints-Cl2XAhzV.js → config-hints-CTqBnCDp.js} +0 -0
@@ -24,16 +24,17 @@ export function buildRawPreviewBlock(params: {
24
24
  oldStartLine?: number | null;
25
25
  newStartLine?: number | null;
26
26
  }): ParsedBlock | null {
27
- const previewText = params.text.trim();
27
+ const { newStartLine: rawNewStartLine, oldStartLine: rawOldStartLine, operation, path, text } = params;
28
+ const previewText = text.trim();
28
29
  if (!previewText) {
29
30
  return null;
30
31
  }
31
32
  const previewKind =
32
- params.operation?.trim().toLowerCase() === "write" ? "add" : "context";
33
+ operation?.trim().toLowerCase() === "write" ? "add" : "context";
33
34
  const oldStartLine =
34
- typeof params.oldStartLine === "number" ? params.oldStartLine : 1;
35
+ typeof rawOldStartLine === "number" ? rawOldStartLine : 1;
35
36
  const newStartLine =
36
- typeof params.newStartLine === "number" ? params.newStartLine : 1;
37
+ typeof rawNewStartLine === "number" ? rawNewStartLine : 1;
37
38
  const lines = buildPreviewLines({
38
39
  text: previewText,
39
40
  kind: previewKind,
@@ -41,10 +42,10 @@ export function buildRawPreviewBlock(params: {
41
42
  newStartLine,
42
43
  });
43
44
  return {
44
- path: params.path,
45
+ path,
45
46
  display: "preview",
46
47
  caption: buildCaption({
47
- operation: params.operation,
48
+ operation,
48
49
  lines,
49
50
  }),
50
51
  lines,
@@ -62,10 +63,11 @@ export function buildFullReplaceBlock(params: {
62
63
  oldStartLine?: number | null;
63
64
  newStartLine?: number | null;
64
65
  }): ParsedBlock | null {
66
+ const { afterText, beforeText, operation, path } = params;
65
67
  const { oldStartLine, newStartLine } = readDefaultDiffStartLines(params);
66
68
  const lines = buildLineDiff({
67
- beforeText: params.beforeText ?? "",
68
- afterText: params.afterText ?? "",
69
+ beforeText: beforeText ?? "",
70
+ afterText: afterText ?? "",
69
71
  oldStartLine,
70
72
  newStartLine,
71
73
  });
@@ -74,16 +76,16 @@ export function buildFullReplaceBlock(params: {
74
76
  return null;
75
77
  }
76
78
  return {
77
- path: params.path,
79
+ path,
78
80
  display: "diff",
79
81
  caption: buildCaption({
80
- operation: params.operation,
82
+ operation,
81
83
  lines,
82
84
  }),
83
85
  lines: limited.lines,
84
86
  ...(limited.truncated ? { fullLines: lines } : {}),
85
- ...(params.beforeText != null ? { beforeText: params.beforeText } : {}),
86
- ...(params.afterText != null ? { afterText: params.afterText } : {}),
87
+ ...(beforeText != null ? { beforeText } : {}),
88
+ ...(afterText != null ? { afterText } : {}),
87
89
  ...(typeof oldStartLine === "number" ? { oldStartLine } : {}),
88
90
  ...(typeof newStartLine === "number" ? { newStartLine } : {}),
89
91
  truncated: limited.truncated,
@@ -95,23 +97,24 @@ function updateApplyPatchCursor(params: {
95
97
  flushCurrent: () => void;
96
98
  setCurrent: (path: string, operation: string) => void;
97
99
  }): boolean {
98
- if (params.line.startsWith("*** Update File: ")) {
99
- params.flushCurrent();
100
- params.setCurrent(
101
- params.line.slice("*** Update File: ".length).trim(),
100
+ const { flushCurrent, line, setCurrent } = params;
101
+ if (line.startsWith("*** Update File: ")) {
102
+ flushCurrent();
103
+ setCurrent(
104
+ line.slice("*** Update File: ".length).trim(),
102
105
  "update",
103
106
  );
104
107
  return true;
105
108
  }
106
- if (params.line.startsWith("*** Add File: ")) {
107
- params.flushCurrent();
108
- params.setCurrent(params.line.slice("*** Add File: ".length).trim(), "add");
109
+ if (line.startsWith("*** Add File: ")) {
110
+ flushCurrent();
111
+ setCurrent(line.slice("*** Add File: ".length).trim(), "add");
109
112
  return true;
110
113
  }
111
- if (params.line.startsWith("*** Delete File: ")) {
112
- params.flushCurrent();
113
- params.setCurrent(
114
- params.line.slice("*** Delete File: ".length).trim(),
114
+ if (line.startsWith("*** Delete File: ")) {
115
+ flushCurrent();
116
+ setCurrent(
117
+ line.slice("*** Delete File: ".length).trim(),
115
118
  "delete",
116
119
  );
117
120
  return true;
@@ -128,18 +131,18 @@ function appendPatchLine(params: {
128
131
  oldLineNumber?: number;
129
132
  newLineNumber?: number;
130
133
  } {
131
- const { currentLines, line } = params;
134
+ const { currentLines, line, newLineNumber, oldLineNumber } = params;
132
135
  if (line.startsWith("+")) {
133
136
  currentLines.push(
134
137
  createLine({
135
138
  kind: "add",
136
139
  text: line.slice(1),
137
- newLineNumber: params.newLineNumber,
140
+ newLineNumber,
138
141
  }),
139
142
  );
140
143
  return {
141
- oldLineNumber: params.oldLineNumber,
142
- newLineNumber: incrementLineNumber(params.newLineNumber),
144
+ oldLineNumber,
145
+ newLineNumber: incrementLineNumber(newLineNumber),
143
146
  };
144
147
  }
145
148
  if (line.startsWith("-")) {
@@ -147,12 +150,12 @@ function appendPatchLine(params: {
147
150
  createLine({
148
151
  kind: "remove",
149
152
  text: line.slice(1),
150
- oldLineNumber: params.oldLineNumber,
153
+ oldLineNumber,
151
154
  }),
152
155
  );
153
156
  return {
154
- oldLineNumber: incrementLineNumber(params.oldLineNumber),
155
- newLineNumber: params.newLineNumber,
157
+ oldLineNumber: incrementLineNumber(oldLineNumber),
158
+ newLineNumber,
156
159
  };
157
160
  }
158
161
  if (line.startsWith(" ")) {
@@ -160,18 +163,18 @@ function appendPatchLine(params: {
160
163
  createLine({
161
164
  kind: "context",
162
165
  text: line.slice(1),
163
- oldLineNumber: params.oldLineNumber,
164
- newLineNumber: params.newLineNumber,
166
+ oldLineNumber,
167
+ newLineNumber,
165
168
  }),
166
169
  );
167
170
  return {
168
- oldLineNumber: incrementLineNumber(params.oldLineNumber),
169
- newLineNumber: incrementLineNumber(params.newLineNumber),
171
+ oldLineNumber: incrementLineNumber(oldLineNumber),
172
+ newLineNumber: incrementLineNumber(newLineNumber),
170
173
  };
171
174
  }
172
175
  return {
173
- oldLineNumber: params.oldLineNumber,
174
- newLineNumber: params.newLineNumber,
176
+ oldLineNumber,
177
+ newLineNumber,
175
178
  };
176
179
  }
177
180
 
@@ -19,14 +19,15 @@ export function createLine(params: {
19
19
  oldLineNumber?: number;
20
20
  newLineNumber?: number;
21
21
  }): ChatFileOperationLineViewModel {
22
+ const { kind, newLineNumber, oldLineNumber, text } = params;
22
23
  return {
23
- kind: params.kind,
24
- text: params.text,
25
- ...(typeof params.oldLineNumber === "number"
26
- ? { oldLineNumber: params.oldLineNumber }
24
+ kind,
25
+ text,
26
+ ...(typeof oldLineNumber === "number"
27
+ ? { oldLineNumber }
27
28
  : {}),
28
- ...(typeof params.newLineNumber === "number"
29
- ? { newLineNumber: params.newLineNumber }
29
+ ...(typeof newLineNumber === "number"
30
+ ? { newLineNumber }
30
31
  : {}),
31
32
  };
32
33
  }
@@ -51,18 +52,19 @@ export function buildPreviewLines(params: {
51
52
  oldStartLine: number;
52
53
  newStartLine: number;
53
54
  }): ChatFileOperationLineViewModel[] {
54
- return splitLines(params.text).map((line, index) =>
55
- params.kind === "add"
55
+ const { kind, newStartLine, oldStartLine, text } = params;
56
+ return splitLines(text).map((line, index) =>
57
+ kind === "add"
56
58
  ? createLine({
57
59
  kind: "add",
58
60
  text: line,
59
- newLineNumber: params.newStartLine + index,
61
+ newLineNumber: newStartLine + index,
60
62
  })
61
63
  : createLine({
62
64
  kind: "context",
63
65
  text: line,
64
- oldLineNumber: params.oldStartLine + index,
65
- newLineNumber: params.newStartLine + index,
66
+ oldLineNumber: oldStartLine + index,
67
+ newLineNumber: newStartLine + index,
66
68
  }),
67
69
  );
68
70
  }
@@ -73,10 +75,11 @@ function buildFallbackDiffLines(params: {
73
75
  oldStartLine?: number;
74
76
  newStartLine?: number;
75
77
  }): ChatFileOperationLineViewModel[] {
76
- let oldLineNumber = params.oldStartLine;
77
- let newLineNumber = params.newStartLine;
78
+ const { afterLines, beforeLines, newStartLine, oldStartLine } = params;
79
+ let oldLineNumber = oldStartLine;
80
+ let newLineNumber = newStartLine;
78
81
  return [
79
- ...params.beforeLines.map((line) => {
82
+ ...beforeLines.map((line) => {
80
83
  const nextLine = createLine({
81
84
  kind: "remove",
82
85
  text: line,
@@ -85,7 +88,7 @@ function buildFallbackDiffLines(params: {
85
88
  oldLineNumber = incrementLineNumber(oldLineNumber);
86
89
  return nextLine;
87
90
  }),
88
- ...params.afterLines.map((line) => {
91
+ ...afterLines.map((line) => {
89
92
  const nextLine = createLine({
90
93
  kind: "add",
91
94
  text: line,
@@ -101,22 +104,23 @@ function buildLcsMatrix(params: {
101
104
  beforeLines: string[];
102
105
  afterLines: string[];
103
106
  }): number[][] {
107
+ const { afterLines, beforeLines } = params;
104
108
  const matrix: number[][] = Array.from(
105
- { length: params.beforeLines.length + 1 },
106
- () => Array.from({ length: params.afterLines.length + 1 }, () => 0),
109
+ { length: beforeLines.length + 1 },
110
+ () => Array.from({ length: afterLines.length + 1 }, () => 0),
107
111
  );
108
112
  for (
109
- let beforeIndex = params.beforeLines.length - 1;
113
+ let beforeIndex = beforeLines.length - 1;
110
114
  beforeIndex >= 0;
111
115
  beforeIndex -= 1
112
116
  ) {
113
117
  for (
114
- let afterIndex = params.afterLines.length - 1;
118
+ let afterIndex = afterLines.length - 1;
115
119
  afterIndex >= 0;
116
120
  afterIndex -= 1
117
121
  ) {
118
122
  matrix[beforeIndex]![afterIndex] =
119
- params.beforeLines[beforeIndex] === params.afterLines[afterIndex]
123
+ beforeLines[beforeIndex] === afterLines[afterIndex]
120
124
  ? (matrix[beforeIndex + 1]![afterIndex + 1] ?? 0) + 1
121
125
  : Math.max(
122
126
  matrix[beforeIndex + 1]![afterIndex] ?? 0,
@@ -136,33 +140,36 @@ function appendRemainingDiffLines(params: {
136
140
  oldLineNumber?: number;
137
141
  newLineNumber?: number;
138
142
  }): void {
143
+ const { afterIndex, afterLines, beforeIndex, beforeLines, lines, newLineNumber: initialNewLineNumber, oldLineNumber: initialOldLineNumber } = params;
144
+ let oldLineNumber = initialOldLineNumber;
145
+ let newLineNumber = initialNewLineNumber;
139
146
  for (
140
- let index = params.beforeIndex;
141
- index < params.beforeLines.length;
147
+ let index = beforeIndex;
148
+ index < beforeLines.length;
142
149
  index += 1
143
150
  ) {
144
- params.lines.push(
151
+ lines.push(
145
152
  createLine({
146
153
  kind: "remove",
147
- text: params.beforeLines[index] ?? "",
148
- oldLineNumber: params.oldLineNumber,
154
+ text: beforeLines[index] ?? "",
155
+ oldLineNumber,
149
156
  }),
150
157
  );
151
- params.oldLineNumber = incrementLineNumber(params.oldLineNumber);
158
+ oldLineNumber = incrementLineNumber(oldLineNumber);
152
159
  }
153
160
  for (
154
- let index = params.afterIndex;
155
- index < params.afterLines.length;
161
+ let index = afterIndex;
162
+ index < afterLines.length;
156
163
  index += 1
157
164
  ) {
158
- params.lines.push(
165
+ lines.push(
159
166
  createLine({
160
167
  kind: "add",
161
- text: params.afterLines[index] ?? "",
162
- newLineNumber: params.newLineNumber,
168
+ text: afterLines[index] ?? "",
169
+ newLineNumber,
163
170
  }),
164
171
  );
165
- params.newLineNumber = incrementLineNumber(params.newLineNumber);
172
+ newLineNumber = incrementLineNumber(newLineNumber);
166
173
  }
167
174
  }
168
175
 
@@ -172,14 +179,15 @@ export function buildLineDiff(params: {
172
179
  oldStartLine?: number;
173
180
  newStartLine?: number;
174
181
  }): ChatFileOperationLineViewModel[] {
175
- const beforeLines = splitLines(params.beforeText);
176
- const afterLines = splitLines(params.afterText);
182
+ const { afterText, beforeText, newStartLine, oldStartLine } = params;
183
+ const beforeLines = splitLines(beforeText);
184
+ const afterLines = splitLines(afterText);
177
185
  if (beforeLines.length * afterLines.length > MAX_DIFF_MATRIX_CELLS) {
178
186
  return buildFallbackDiffLines({
179
187
  beforeLines,
180
188
  afterLines,
181
- oldStartLine: params.oldStartLine,
182
- newStartLine: params.newStartLine,
189
+ oldStartLine,
190
+ newStartLine,
183
191
  });
184
192
  }
185
193
 
@@ -190,8 +198,8 @@ export function buildLineDiff(params: {
190
198
  const lines: ChatFileOperationLineViewModel[] = [];
191
199
  let beforeIndex = 0;
192
200
  let afterIndex = 0;
193
- let oldLineNumber = params.oldStartLine;
194
- let newLineNumber = params.newStartLine;
201
+ let oldLineNumber = oldStartLine;
202
+ let newLineNumber = newStartLine;
195
203
  while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
196
204
  if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
197
205
  lines.push(
@@ -20,10 +20,11 @@ export function buildCaption(params: {
20
20
  operation?: string | null;
21
21
  lines: ChatFileOperationLineViewModel[];
22
22
  }): string | undefined {
23
- const additions = params.lines.filter((line) => line.kind === "add").length;
24
- const deletions = params.lines.filter((line) => line.kind === "remove").length;
23
+ const { lines, operation } = params;
24
+ const additions = lines.filter((line) => line.kind === "add").length;
25
+ const deletions = lines.filter((line) => line.kind === "remove").length;
25
26
  const parts: string[] = [];
26
- const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
27
+ const normalizedOperation = operation?.trim().toLowerCase() ?? "";
27
28
  if (normalizedOperation && normalizedOperation !== "update") {
28
29
  parts.push(normalizedOperation);
29
30
  }
@@ -46,20 +47,21 @@ export function readDefaultDiffStartLines(params: {
46
47
  oldStartLine?: number;
47
48
  newStartLine?: number;
48
49
  } {
49
- const normalizedOperation = params.operation?.trim().toLowerCase() ?? "";
50
+ const { afterText, beforeText, newStartLine: rawNewStartLine, oldStartLine: rawOldStartLine, operation } = params;
51
+ const normalizedOperation = operation?.trim().toLowerCase() ?? "";
50
52
  const oldStartLine =
51
- typeof params.oldStartLine === "number"
52
- ? params.oldStartLine
53
+ typeof rawOldStartLine === "number"
54
+ ? rawOldStartLine
53
55
  : (normalizedOperation === "delete" ||
54
56
  normalizedOperation === "remove") &&
55
- params.beforeText != null
57
+ beforeText != null
56
58
  ? 1
57
59
  : undefined;
58
60
  const newStartLine =
59
- typeof params.newStartLine === "number"
60
- ? params.newStartLine
61
+ typeof rawNewStartLine === "number"
62
+ ? rawNewStartLine
61
63
  : (normalizedOperation === "write" || normalizedOperation === "add") &&
62
- params.afterText != null
64
+ afterText != null
63
65
  ? 1
64
66
  : undefined;
65
67
  return { oldStartLine, newStartLine };
@@ -83,16 +85,17 @@ export function buildParsedPatchBlock(params: {
83
85
  operation: string | null;
84
86
  lines: ChatFileOperationLineViewModel[];
85
87
  }): ParsedBlock {
86
- const limited = limitLines(params.lines);
88
+ const { lines, operation, path } = params;
89
+ const limited = limitLines(lines);
87
90
  return {
88
- path: params.path,
91
+ path,
89
92
  display: "diff",
90
93
  caption: buildCaption({
91
- operation: params.operation,
92
- lines: params.lines,
94
+ operation,
95
+ lines,
93
96
  }),
94
97
  lines: limited.lines,
95
- ...(limited.truncated ? { fullLines: params.lines } : {}),
98
+ ...(limited.truncated ? { fullLines: lines } : {}),
96
99
  truncated: limited.truncated,
97
100
  };
98
101
  }
@@ -32,6 +32,7 @@ describe('useUiShowContentEvent', () => {
32
32
  },
33
33
  title: 'Example URL',
34
34
  purpose: 'read' as const,
35
+ placement: undefined,
35
36
  };
36
37
 
37
38
  const rendered = render(
@@ -6,13 +6,16 @@ import type { ConfigView, ProvidersView, ProviderTemplatesView } from '@/shared/
6
6
  import type { ChatModelOption } from '@/features/chat/types/chat-input.types';
7
7
  import { buildProviderModelCatalog, composeProviderModel, resolveModelThinkingCapability } from '@/shared/lib/provider-models';
8
8
 
9
- function buildRuntimeDefaultModelOption(label: string): ChatModelOption {
9
+ function buildRuntimeDefaultModelOption(
10
+ label: string,
11
+ thinkingCapability?: ChatModelOption['thinkingCapability'],
12
+ ): ChatModelOption {
10
13
  return {
11
14
  value: RUNTIME_DEFAULT_MODEL_VALUE,
12
15
  modelLabel: label,
13
16
  providerLabel: '',
14
17
  isRuntimeDefault: true,
15
- thinkingCapability: null,
18
+ thinkingCapability: thinkingCapability ?? null,
16
19
  };
17
20
  }
18
21
 
@@ -50,16 +53,21 @@ export function buildNcpChatProviderModelOptions(params: {
50
53
  export function filterNcpChatModelOptionsBySessionType(params: {
51
54
  modelOptions: ChatModelOption[];
52
55
  modelSelectionMode?: RuntimeModelSelectionMode;
56
+ runtimeDefaultThinkingCapability?: ChatModelOption['thinkingCapability'];
53
57
  runtimeDefaultModelLabel?: string;
54
58
  supportedModels?: string[];
55
59
  }): ChatModelOption[] {
56
60
  const {
57
61
  modelOptions,
58
62
  modelSelectionMode,
63
+ runtimeDefaultThinkingCapability,
59
64
  runtimeDefaultModelLabel = 'Runtime default',
60
65
  supportedModels,
61
66
  } = params;
62
- const runtimeDefaultOption = buildRuntimeDefaultModelOption(runtimeDefaultModelLabel);
67
+ const runtimeDefaultOption = buildRuntimeDefaultModelOption(
68
+ runtimeDefaultModelLabel,
69
+ runtimeDefaultThinkingCapability,
70
+ );
63
71
  if (modelSelectionMode === 'runtime-default') {
64
72
  return [runtimeDefaultOption];
65
73
  }
@@ -45,6 +45,7 @@ describe('ChatSessionHeaderActions', () => {
45
45
 
46
46
  expect(screen.getByText('Set Project Directory')).toBeTruthy();
47
47
  expect(screen.queryByText('Clear Project Directory')).toBeNull();
48
+ expect(screen.getByText('View Metadata')).toBeTruthy();
48
49
  expect(screen.getByText('Delete Session')).toBeTruthy();
49
50
  });
50
51
 
@@ -67,6 +68,32 @@ describe('ChatSessionHeaderActions', () => {
67
68
 
68
69
  expect(screen.getByText('Set Project Directory')).toBeTruthy();
69
70
  expect(screen.queryByText('Clear Project Directory')).toBeNull();
71
+ expect(screen.getByText('View Metadata')).toBeTruthy();
72
+ });
73
+
74
+ it('opens the session metadata dialog from the more-actions menu', async () => {
75
+ const user = userEvent.setup();
76
+
77
+ render(
78
+ <ChatSessionHeaderActions
79
+ sessionKey="session-codex"
80
+ canDeleteSession
81
+ isDeletePending={false}
82
+ projectRoot={null}
83
+ metadata={{
84
+ codex_thread_id: 'thread-123',
85
+ runtime: 'codex',
86
+ }}
87
+ onDeleteSession={mocks.onDeleteSession}
88
+ />
89
+ );
90
+
91
+ await user.click(screen.getByRole('button', { name: 'More actions' }));
92
+ await user.click(screen.getByRole('button', { name: 'View Metadata' }));
93
+
94
+ expect(screen.getByRole('dialog', { name: 'Session Metadata' })).toBeTruthy();
95
+ expect(screen.getByText(/codex_thread_id/)).toBeTruthy();
96
+ expect(screen.getByText(/thread-123/)).toBeTruthy();
70
97
  });
71
98
 
72
99
  it('shows a dedicated child-session entry button when the current session has child sessions', async () => {
@@ -2,6 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import { ChatSessionProjectBadge } from '@/features/chat/features/session/components/session-header/chat-session-project-badge';
5
+ import { createPopoverAvailableHeightLimit } from '@/shared/components/ui/popover';
5
6
 
6
7
  const mocks = vi.hoisted(() => ({
7
8
  updateSessionProject: vi.fn(),
@@ -15,6 +16,17 @@ vi.mock('@/features/chat/features/session/components/session-header/chat-session
15
16
  ChatSessionProjectDialog: () => null,
16
17
  }));
17
18
 
19
+ function renderProjectBadge() {
20
+ return render(
21
+ <ChatSessionProjectBadge
22
+ sessionKey="session-1"
23
+ projectName="project-alpha"
24
+ projectRoot="/tmp/project-alpha"
25
+ persistToServer
26
+ />
27
+ );
28
+ }
29
+
18
30
  describe('ChatSessionProjectBadge', () => {
19
31
  beforeEach(() => {
20
32
  mocks.updateSessionProject.mockReset();
@@ -24,31 +36,26 @@ describe('ChatSessionProjectBadge', () => {
24
36
  it('shows project actions inside the badge popover', async () => {
25
37
  const user = userEvent.setup();
26
38
 
27
- render(
28
- <ChatSessionProjectBadge
29
- sessionKey="session-1"
30
- projectName="project-alpha"
31
- projectRoot="/tmp/project-alpha"
32
- persistToServer
33
- />
34
- );
39
+ renderProjectBadge();
35
40
 
36
41
  await user.click(screen.getByRole('button', { name: 'Set Project Directory' }));
37
42
 
38
43
  expect(screen.getAllByText('Set Project Directory').length).toBeGreaterThan(0);
39
44
  expect(screen.getByText('Clear Project Directory')).toBeTruthy();
40
- expect(screen.getByText('/tmp/project-alpha')).toBeTruthy();
45
+ const projectRoot = screen.getByText('/tmp/project-alpha');
46
+ expect(projectRoot).toBeTruthy();
47
+
48
+ const boundedPopover = projectRoot.closest('[style*="max-height"]') as HTMLElement | null;
49
+ expect(boundedPopover?.style.maxHeight).toBe(
50
+ createPopoverAvailableHeightLimit('18rem')
51
+ );
52
+ expect(boundedPopover?.style.maxHeight).toContain('max(0px');
53
+ expect(boundedPopover?.style.maxHeight).toContain('100vh');
54
+ expect(boundedPopover?.style.maxHeight).toContain('2rem');
41
55
  });
42
56
 
43
57
  it('uses the neutral header tag styling instead of a highlighted accent color', () => {
44
- render(
45
- <ChatSessionProjectBadge
46
- sessionKey="session-1"
47
- projectName="project-alpha"
48
- projectRoot="/tmp/project-alpha"
49
- persistToServer
50
- />
51
- );
58
+ renderProjectBadge();
52
59
 
53
60
  const trigger = screen.getByRole('button', { name: 'Set Project Directory' });
54
61
  expect(trigger.className).toContain('border-gray-200');
@@ -59,14 +66,7 @@ describe('ChatSessionProjectBadge', () => {
59
66
  it('clears the current project from the badge popover', async () => {
60
67
  const user = userEvent.setup();
61
68
 
62
- render(
63
- <ChatSessionProjectBadge
64
- sessionKey="session-1"
65
- projectName="project-alpha"
66
- projectRoot="/tmp/project-alpha"
67
- persistToServer
68
- />
69
- );
69
+ renderProjectBadge();
70
70
 
71
71
  await user.click(screen.getByRole('button', { name: 'Set Project Directory' }));
72
72
  await user.click(screen.getByText('Clear Project Directory'));
@@ -1,9 +1,10 @@
1
1
  import { useState } from 'react';
2
- import { AlarmClock, FolderOpen, GitBranch, MoreVertical, Trash2 } from 'lucide-react';
2
+ import { AlarmClock, Braces, FolderOpen, GitBranch, MoreVertical, Trash2 } from 'lucide-react';
3
3
  import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
4
4
  import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
5
5
  import { useChatSessionProject } from '@/features/chat/features/session/hooks/use-chat-session-project';
6
6
  import { ChatSessionHeaderMenuItem } from './chat-session-header-menu-item';
7
+ import { ChatSessionMetadataDialog } from './chat-session-metadata-dialog';
7
8
  import { ChatSessionProjectDialog } from './chat-session-project-dialog';
8
9
  import { t } from '@/shared/lib/i18n';
9
10
 
@@ -15,6 +16,7 @@ type ChatSessionHeaderActionsProps = {
15
16
  canDeleteSession: boolean;
16
17
  isDeletePending: boolean;
17
18
  projectRoot?: string | null;
19
+ metadata?: Record<string, unknown> | null;
18
20
  childSessionCount?: number;
19
21
  sessionCronJobCount?: number;
20
22
  onOpenChildSessions?: () => void;
@@ -27,6 +29,7 @@ export function ChatSessionHeaderActions({
27
29
  canDeleteSession,
28
30
  isDeletePending,
29
31
  projectRoot,
32
+ metadata,
30
33
  childSessionCount = 0,
31
34
  sessionCronJobCount = 0,
32
35
  onOpenChildSessions,
@@ -36,6 +39,7 @@ export function ChatSessionHeaderActions({
36
39
  const updateSessionProject = useChatSessionProject();
37
40
  const [isMenuOpen, setIsMenuOpen] = useState(false);
38
41
  const [isDialogOpen, setIsDialogOpen] = useState(false);
42
+ const [isMetadataDialogOpen, setIsMetadataDialogOpen] = useState(false);
39
43
  const [isProjectPending, setIsProjectPending] = useState(false);
40
44
  const isBusy = isDeletePending || isProjectPending;
41
45
 
@@ -96,6 +100,15 @@ export function ChatSessionHeaderActions({
96
100
  }}
97
101
  disabled={isBusy}
98
102
  />
103
+ <ChatSessionHeaderMenuItem
104
+ icon={Braces}
105
+ label={t('chatSessionViewMetadata')}
106
+ onClick={() => {
107
+ setIsMenuOpen(false);
108
+ setIsMetadataDialogOpen(true);
109
+ }}
110
+ disabled={isBusy}
111
+ />
99
112
  <ChatSessionHeaderMenuItem
100
113
  icon={Trash2}
101
114
  label={t('chatDeleteSession')}
@@ -117,6 +130,11 @@ export function ChatSessionHeaderActions({
117
130
  onOpenChange={setIsDialogOpen}
118
131
  onSave={runProjectUpdate}
119
132
  />
133
+ <ChatSessionMetadataDialog
134
+ open={isMetadataDialogOpen}
135
+ metadata={metadata}
136
+ onOpenChange={setIsMetadataDialogOpen}
137
+ />
120
138
  </div>
121
139
  );
122
140
  }