@kortix/ui 0.14.5 → 0.14.7

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 (236) hide show
  1. package/dist/_vendor/kortix-shared.js +489 -381
  2. package/dist/access/access/access-dialog.js +8 -5
  3. package/dist/access/access/copy-row.js +2 -2
  4. package/dist/access/sharing-picker.js +2 -1
  5. package/dist/browser/app-in-browser.js +5 -3
  6. package/dist/browser/browser-panel.js +6 -6
  7. package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
  8. package/dist/browser/sandbox-browser-chrome.js +4 -3
  9. package/dist/customize/agents/agent-access.d.ts +20 -0
  10. package/dist/customize/agents/agent-access.js +17 -0
  11. package/dist/customize/agents/agent-authority-card.js +1 -2
  12. package/dist/customize/agents/agent-detail-aside.js +15 -14
  13. package/dist/customize/agents/agent-drives-section.js +5 -6
  14. package/dist/customize/agents/agent-grant-pages.js +73 -76
  15. package/dist/customize/agents/agent-nav.prototype.d.ts +35 -0
  16. package/dist/customize/agents/agent-nav.prototype.js +95 -0
  17. package/dist/customize/agents/agent-page.js +59 -23
  18. package/dist/customize/agents/agent-people-section.js +18 -17
  19. package/dist/customize/agents/agent-triggers-section.js +6 -5
  20. package/dist/customize/agents/agents-page.js +18 -1
  21. package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
  22. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  23. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  24. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  25. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  26. package/dist/customize/connectors/connector-routes.d.ts +3 -1
  27. package/dist/customize/connectors/connector-routes.js +18 -0
  28. package/dist/customize/connectors/connectors-page.js +23 -4
  29. package/dist/customize/connectors/detail/app-page.js +19 -5
  30. package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
  31. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  32. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  33. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  34. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  35. package/dist/customize/connectors/detail/connector-page.js +26 -13
  36. package/dist/customize/providers/provider-accordion.js +2 -1
  37. package/dist/customize/providers/provider-connect.js +2 -1
  38. package/dist/customize/sections/component/telegram-channel-row.js +1 -1
  39. package/dist/customize/sections/connector-connection-modal.js +11 -9
  40. package/dist/customize/sections/connectors-view.js +11 -13
  41. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  42. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  43. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  44. package/dist/customize/sections/llm-provider/utils.js +2 -2
  45. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  46. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  47. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  48. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  49. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  50. package/dist/customize/sections/view/agent-editor.js +3 -3
  51. package/dist/customize/sections/view/gateway/gateway-routing.js +18 -21
  52. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  53. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  54. package/dist/customize/sections/view/permission-editor.js +11 -8
  55. package/dist/customize/sections/view/secrets-view.js +2 -1
  56. package/dist/customize/settings/rail-copy-context.js +5 -3
  57. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  58. package/dist/customize/settings/tabs/general-tab.js +33 -11
  59. package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
  60. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  61. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  62. package/dist/customize/shared/capability-tabs.js +2 -1
  63. package/dist/customize/skills/skills-page.js +3 -5
  64. package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
  65. package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
  66. package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
  67. package/dist/customize/triggers/schedule/when-event.js +1 -1
  68. package/dist/diff/diff-view.js +9 -10
  69. package/dist/files/editor/code-editor.js +2 -3
  70. package/dist/files/project/api/runtime-files.js +3 -2
  71. package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
  72. package/dist/files/project/components/change-request-detail-dialog.js +3 -5
  73. package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
  74. package/dist/files/project/components/change-request-status-badge.js +27 -0
  75. package/dist/files/project/components/change-requests-panel.js +1 -1
  76. package/dist/files/project/components/drive-explorer.js +2 -1
  77. package/dist/files/project/components/drive-grid-view.js +11 -7
  78. package/dist/files/project/components/drive-list-view.js +4 -4
  79. package/dist/files/project/components/file-breadcrumbs.js +1 -1
  80. package/dist/files/project/components/file-explorer-page.js +1 -1
  81. package/dist/files/project/components/file-history-popover.js +1 -6
  82. package/dist/files/project/components/file-search.js +6 -1
  83. package/dist/files/project/components/open-change-request-dialog.js +2 -1
  84. package/dist/files/project/components/version-selector-logic.d.ts +11 -0
  85. package/dist/files/project/components/version-selector-logic.js +26 -0
  86. package/dist/files/project/components/version-selector.js +11 -20
  87. package/dist/files/project/file-source.js +18 -1
  88. package/dist/files/project/hooks/use-file-content.js +2 -0
  89. package/dist/files/project/hooks/use-server-health.js +17 -15
  90. package/dist/files/renderers/docx/docx-viewer.js +0 -5
  91. package/dist/files/renderers/image-renderer.js +6 -24
  92. package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
  93. package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
  94. package/dist/files/renderers/show-content-renderer.js +1 -1
  95. package/dist/files/renderers/sqlite-renderer.js +8 -3
  96. package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
  97. package/dist/files/tree/components/file-tree-item.js +1 -1
  98. package/dist/files/viewer/file-header.js +1 -1
  99. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  100. package/dist/files/viewer/file-preview-modal.js +13 -3
  101. package/dist/files/viewer/file-source.d.ts +3 -1
  102. package/dist/files/viewer/use-file-content-state.js +27 -8
  103. package/dist/files/viewer/viewer-actions.js +2 -2
  104. package/dist/i18n/en.d.ts +355 -12
  105. package/dist/i18n/en.js +360 -17
  106. package/dist/internal/desktop/desktop-back-button.js +5 -3
  107. package/dist/internal/files/types.d.ts +2 -0
  108. package/dist/internal/goals/goal-meta.d.ts +30 -0
  109. package/dist/internal/goals/goal-meta.js +55 -0
  110. package/dist/internal/hooks/use-auto-scroll.js +5 -3
  111. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  112. package/dist/internal/lib/query-client-singleton.js +18 -12
  113. package/dist/internal/notifications/notification-rows.js +2 -1
  114. package/dist/internal/outcomes/outcome-types.d.ts +5 -0
  115. package/dist/internal/share/session-shares-modal.js +3 -7
  116. package/dist/internal/stores/kortix-computer-store.js +14 -2
  117. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  118. package/dist/internal/stores/session-hover-store.js +38 -0
  119. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  120. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  121. package/dist/lib/routes.d.ts +2 -0
  122. package/dist/lib/routes.js +1 -0
  123. package/dist/markdown/code/inline-code.js +8 -0
  124. package/dist/markdown/code/output-paths-context.d.ts +18 -0
  125. package/dist/markdown/code/output-paths-context.js +35 -0
  126. package/dist/markdown/genui/components/charts.js +6 -5
  127. package/dist/primitives/map.js +2 -1
  128. package/dist/primitives/preview-image.js +4 -2
  129. package/dist/primitives/settings-section-header.d.ts +1 -1
  130. package/dist/primitives/textarea.js +1 -1
  131. package/dist/primitives/typed-title.js +12 -14
  132. package/dist/primitives/typing-animation.js +4 -2
  133. package/dist/session/composer/agent-selector.js +6 -1
  134. package/dist/session/composer/attachment-tile.d.ts +0 -2
  135. package/dist/session/composer/attachment-tile.js +0 -8
  136. package/dist/session/composer/clipboard-files.d.ts +2 -1
  137. package/dist/session/composer/composer-chat-input.js +5 -3
  138. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  139. package/dist/session/composer/composer-draft-recovery.js +0 -13
  140. package/dist/session/composer/composer-logic.js +8 -7
  141. package/dist/session/composer/context-ring.d.ts +1 -1
  142. package/dist/session/composer/context-ring.js +2 -2
  143. package/dist/session/composer/context-usage.d.ts +3 -6
  144. package/dist/session/composer/context-usage.js +3 -6
  145. package/dist/session/composer/draft/composer-draft.js +4 -4
  146. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  147. package/dist/session/composer/inbox-row-claims.js +1 -1
  148. package/dist/session/composer/pasted-text.js +3 -2
  149. package/dist/session/composer/queue-projection.d.ts +3 -2
  150. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  151. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  152. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  153. package/dist/session/composer/session-composer-readiness.js +12 -40
  154. package/dist/session/composer/stores/queued-draft-store.js +3 -2
  155. package/dist/session/composer/token-progress.d.ts +1 -1
  156. package/dist/session/composer/token-progress.js +2 -2
  157. package/dist/session/header/reminder-parts.d.ts +11 -2
  158. package/dist/session/header/reminder-parts.js +9 -4
  159. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  160. package/dist/session/header/session-goal-indicator.js +41 -0
  161. package/dist/session/header/session-site-header.js +2 -1
  162. package/dist/session/media/sandbox-url-detector.js +0 -7
  163. package/dist/session/outcomes/change-request-outcomes.js +24 -3
  164. package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
  165. package/dist/session/panel/advanced/advanced-panel.js +3 -7
  166. package/dist/session/panel/easy/app-preview.js +12 -2
  167. package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
  168. package/dist/session/panel/easy/easy-panel-logic.js +1 -1
  169. package/dist/session/panel/easy/outputs-card.js +2 -1
  170. package/dist/session/panel/easy/panel-card.js +5 -3
  171. package/dist/session/panel/easy/preview-load.d.ts +14 -0
  172. package/dist/session/panel/easy/preview-load.js +15 -0
  173. package/dist/session/panel/easy/step-detail-body.js +2 -7
  174. package/dist/session/panel/session-action-panel-column.js +11 -1
  175. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  176. package/dist/session/panel/session-audit-shared.js +1 -1
  177. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  178. package/dist/session/panel/session-panel-provider.js +6 -2
  179. package/dist/session/panel/shared/use-panel-derivations.js +6 -4
  180. package/dist/session/panel/use-session-panel-layout.js +7 -5
  181. package/dist/session/print/session-print-header.js +9 -8
  182. package/dist/session/prompts/session-permission-prompt.js +6 -3
  183. package/dist/session/status/provisioning-failure.d.ts +2 -1
  184. package/dist/session/status/session-error-banner.js +8 -6
  185. package/dist/session/status/session-starting-loader.d.ts +24 -31
  186. package/dist/session/status/session-starting-loader.js +32 -81
  187. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  188. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  189. package/dist/session/thread/message-parsing.d.ts +3 -3
  190. package/dist/session/thread/session-chat.js +15 -19
  191. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  192. package/dist/session/thread/session-history.js +3 -1
  193. package/dist/session/thread/session-thread.js +1 -1
  194. package/dist/session/thread/transcript.js +13 -7
  195. package/dist/session/tool/shared/file-list.js +19 -2
  196. package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
  197. package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
  198. package/dist/session/tool/shared/infrastructure-output.js +9 -5
  199. package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
  200. package/dist/session/tool/shared/infrastructure-rows.js +7 -5
  201. package/dist/session/tool/tools/bash-tool.js +10 -6
  202. package/dist/session/tool/tools/web-search-tool.js +25 -3
  203. package/dist/session/turn/recovered-failures.d.ts +8 -0
  204. package/dist/session/turn/recovered-failures.js +32 -0
  205. package/dist/session/turn/streaming-cadence.js +5 -8
  206. package/dist/styles.css +1 -1
  207. package/dist/terminal/pty-connection.d.ts +5 -4
  208. package/dist/terminal/session-terminal-panel.js +3 -5
  209. package/dist/theme/theme.js +2 -1
  210. package/dist/workspace/index.d.ts +1 -0
  211. package/dist/workspace/index.js +1 -0
  212. package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
  213. package/dist/workspace/review/review-center.js +13 -10
  214. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  215. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  216. package/dist/workspace/saved-session-skeleton.js +5 -4
  217. package/dist/workspace/session-workspace.js +9 -14
  218. package/dist/workspace/sessions/deleted-session-row.js +5 -2
  219. package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
  220. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  221. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  222. package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
  223. package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
  224. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  225. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  226. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  227. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  228. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  229. package/dist/workspace/sidebar/project-session-list.js +12 -3
  230. package/dist/workspace/sidebar/session-brief-hover-card.js +5 -8
  231. package/package.json +4 -4
  232. package/skills/kortix-ui/references/modules.md +2 -0
  233. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  234. package/dist/session/status/use-readiness-settling.js +0 -43
  235. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  236. package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
@@ -1,33 +1,44 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- /**
4
- * Layout primitives for the agent editor. Three shapes and nothing else: a
5
- * titled section, a row (label left, control right), and a block (label above
6
- * a full-width control).
7
- *
8
- * There is deliberately NO icon slot. Every group used to open with a glyph —
9
- * a stack, a chip, a gauge, a shield — and not one of them named anything the
10
- * words beside it did not already say. Type carries the hierarchy: section
11
- * titles at `text-sm font-medium`, help at `text-xs text-muted-foreground`.
12
- * Those two sizes are the whole scale; nothing in the editor is smaller.
13
- */
14
3
  import { createContext, useContext } from 'react';
4
+ import { SettingsRowGroup } from '../../../primitives/settings-row.js';
5
+ import { SettingsSectionHeader } from '../../../primitives/settings-section-header.js';
6
+ import { SettingsSubsectionHeader } from '../../../primitives/settings-subsection-header.js';
15
7
  import { cn } from '../../../internal/lib/utils.js';
16
8
  const EditorSectionStyleContext = createContext('flat');
17
9
  export function EditorSectionStyleProvider({ value, children, }) {
18
10
  return (_jsx(EditorSectionStyleContext.Provider, { value: value, children: children }));
19
11
  }
20
12
  /**
21
- * One titled group of settings. Rows inside are separated by hairlines rather
22
- * than boxed individually. See `EditorSectionStyle` for the two dialects.
13
+ * One titled group of settings, in the Settings page's dialect
14
+ * (`settings/tabs/general-tab.tsx`): the heading sits on the page, never
15
+ * inside a box, and only the rows below it share one bordered group.
16
+ *
17
+ * `lead` marks the first section of a pane. It takes the pane title
18
+ * (`SettingsSectionHeader`, `text-base lg:text-xl`). Every later section
19
+ * takes the quieter `SettingsSubsectionHeader`. Neither shows `description`
20
+ * on the agent page; it renders only in the legacy `flat` style.
21
+ *
22
+ * `bare` hands the body to the caller unwrapped: a list (`GrantList`), a
23
+ * markdown preview, or a textarea draws its own surface.
23
24
  */
24
- export function EditorSection({ title, description, trailing, children, }) {
25
+ export function EditorSection({ title, description, trailing, lead = false, bare = false, children, }) {
25
26
  const style = useContext(EditorSectionStyleContext);
26
- const heading = (_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground text-xs leading-relaxed text-pretty", children: description })) : null] }), trailing ? _jsx("div", { className: "shrink-0", children: trailing }) : null] }));
27
27
  if (style === 'panel') {
28
- return (_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsx("div", { className: "border-border/60 border-b px-4 pt-4 pb-3", children: heading }), _jsx("div", { className: cn('divide-border/60 divide-y px-4'), children: children })] }));
28
+ return (_jsxs("section", { className: lead ? 'space-y-6' : 'space-y-3', children: [lead ? (
29
+ // Headings stand alone (Jay, 2026-10-20): a sentence under every
30
+ // title made the page read as prose.
31
+ _jsx(SettingsSectionHeader, { title: title, action: trailing, className: "pb-1" })) : (_jsx(SettingsSubsectionHeader, { title: title, action: trailing })), bare ? children : _jsx(SettingsRowGroup, { children: children })] }));
29
32
  }
30
- return (_jsxs("section", { className: "space-y-2", children: [heading, _jsx("div", { className: "divide-border/60 divide-y", children: children })] }));
33
+ return (_jsxs("section", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 space-y-1", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground text-xs leading-relaxed text-pretty", children: description })) : null] }), trailing ? _jsx("div", { className: "shrink-0", children: trailing }) : null] }), _jsx("div", { className: "divide-border/60 divide-y", children: children })] }));
34
+ }
35
+ /**
36
+ * The bordered list a section hands its rows to: one `bg-popover` group,
37
+ * hairlines between rows, no per-row box. A stack of individually bordered
38
+ * cards read as ten announcements; one group reads as one list.
39
+ */
40
+ export function EditorList({ className, ...props }) {
41
+ return (_jsx("ul", { className: cn('bg-popover divide-border divide-y overflow-hidden rounded-md border', className), ...props }));
31
42
  }
32
43
  /**
33
44
  * Label and help on the left, control on the right — for anything that fits a
@@ -38,14 +49,16 @@ export function EditorSection({ title, description, trailing, children, }) {
38
49
  * three deep.
39
50
  */
40
51
  export function SettingRow({ label, help, children, }) {
41
- return (_jsxs("div", { className: "flex flex-col gap-2 py-3.5 sm:flex-row sm:items-center sm:justify-between sm:gap-6", children: [_jsx(SettingLabel, { label: label, help: help }), _jsx("div", { className: "w-full shrink-0 sm:w-52", children: children })] }));
52
+ const style = useContext(EditorSectionStyleContext);
53
+ return (_jsxs("div", { className: cn('flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-6', style === 'panel' ? 'px-4 py-3' : 'py-3.5'), children: [_jsx(SettingLabel, { label: label, help: help }), _jsx("div", { className: "w-full shrink-0 sm:w-52", children: children })] }));
42
54
  }
43
55
  /**
44
56
  * Label and help above a full-width control — for anything a row cannot hold:
45
57
  * a textarea, a grant checklist, the permission tree.
46
58
  */
47
59
  export function SettingBlock({ label, help, children, }) {
48
- return (_jsxs("div", { className: "space-y-2.5 py-3.5", children: [_jsx(SettingLabel, { label: label, help: help }), children] }));
60
+ const style = useContext(EditorSectionStyleContext);
61
+ return (_jsxs("div", { className: cn('space-y-2.5', style === 'panel' ? 'px-4 py-3' : 'py-3.5'), children: [_jsx(SettingLabel, { label: label, help: help }), children] }));
49
62
  }
50
63
  /**
51
64
  * The label pair. A plain `<span>`, not `<Label>`: none of these controls take
@@ -317,8 +317,8 @@ export function isAgentConfigSectionKey(value) {
317
317
  */
318
318
  export function AgentConfigSections({ section, editor, options, skillsOptions, registryOptions, overview, triggers, people, skills, connectors, secrets, apps, drives, authority, }) {
319
319
  const { draft, oc, set, setOc } = editor;
320
- // Every section is a card (`EditorSectionStyle` 'panel'), so a tab holding
321
- // two of them — Access holds the people and the grants — shows two blocks.
320
+ // Every pane opens with a `lead` section (the pane title) and stacks its
321
+ // later sections under Settings subsection headings.
322
322
  const body = (() => {
323
323
  switch (section) {
324
324
  case 'overview':
@@ -353,6 +353,6 @@ export function AgentConfigSections({ section, editor, options, skillsOptions, r
353
353
  return (_jsx(BasicsSection, { draft: draft, set: set, oc: oc, setOc: setOc, showDescription: false }));
354
354
  }
355
355
  })();
356
- return (_jsx(EditorSectionStyleProvider, { value: "panel", children: _jsx("div", { className: "space-y-4", children: body }) }));
356
+ return (_jsx(EditorSectionStyleProvider, { value: "panel", children: _jsx("div", { className: "space-y-8", children: body }) }));
357
357
  }
358
358
  export { grantSummary } from './grant-mode-field.js';
@@ -47,13 +47,13 @@ import { useTranslations } from '../../../../i18n/index.js';
47
47
  *
48
48
  * | Capability | Where it is now |
49
49
  * | --- | --- |
50
- * | Inherit / custom / no fallback | "When the default fails" row (primary) |
50
+ * | Inherit / custom / no fallback | "When a model fails" row (primary) |
51
51
  * | Ordered default chain, reorder + remove, max 8 | Chain row — one line per step (primary) |
52
52
  * | Fallback condition (transient / any error) | "Retry on" line, inside the chain it belongs to |
53
53
  * | Per-model override rules, max 20 | "Per-model overrides" — one row per rule, promoted second (primary) |
54
54
  * | Vision (image-input) override | "Vision model" row, inside Advanced |
55
55
  * | Per-model generation defaults | "Generation defaults" section, inside Advanced |
56
- * | Provider-availability preview | `AvailabilityBadge`, unchanged, now also on the primary |
56
+ * | Provider-availability preview | `AvailabilityBadge`, on every chain step and override |
57
57
  * | Reset / dirty / validation / save | Header action + sticky footer, unchanged |
58
58
  *
59
59
  * The data contract is untouched: same props in, same `routing.set` /
@@ -287,26 +287,23 @@ export function GatewayRouting({ projectId, canWrite, projectDefaultPending, })
287
287
  const [advancedOpen, setAdvancedOpen] = useState(false);
288
288
  const [resetOpen, setResetOpen] = useState(false);
289
289
  const [availability, setAvailability] = useState({});
290
- const hydratedPolicySignature = useRef(null);
290
+ const [hydratedPolicySignature, setHydratedPolicySignature] = useState(null);
291
291
  const previewRequestId = useRef(0);
292
- useEffect(() => {
293
- if (!routing.data?.project)
294
- return;
295
- const signature = editablePolicySignature(routing.data.project);
296
- // Effective routing also changes when the header's project default changes.
297
- // Keep an in-progress fallback draft intact unless the persisted project
298
- // policy itself changed (save, reset, or an external editor update).
299
- if (hydratedPolicySignature.current === signature)
300
- return;
301
- hydratedPolicySignature.current = signature;
302
- setDraft(clonePolicy(routing.data.project));
303
- setFallbackMode(fallbackModeForPolicy(routing.data.project.defaultFallback));
304
- setAdvancedOpen((current) => current || hasAdvancedRoutingConfig(routing.data.project));
305
- }, [routing.data]);
292
+ // Effective routing also changes when the header's project default changes.
293
+ // Keep an in-progress fallback draft intact unless the persisted project
294
+ // policy itself changed (save, reset, or an external editor update).
295
+ const persistedProject = routing.data?.project;
296
+ const persistedSignature = persistedProject ? editablePolicySignature(persistedProject) : null;
297
+ if (persistedProject && persistedSignature !== hydratedPolicySignature) {
298
+ setHydratedPolicySignature(persistedSignature);
299
+ setDraft(clonePolicy(persistedProject));
300
+ setFallbackMode(fallbackModeForPolicy(persistedProject.defaultFallback));
301
+ setAdvancedOpen((current) => current || hasAdvancedRoutingConfig(persistedProject));
302
+ }
303
+ const previewRoute = routing.preview.mutateAsync;
306
304
  // Debounced availability preview: whenever the draft's referenced models
307
305
  // change, ask the gateway which of them currently have a connected
308
306
  // provider so the chain editor can flag a dead fallback before save.
309
- // biome-ignore lint/correctness/useExhaustiveDependencies: routing.preview's mutateAsync is a stable handle from useGatewayRoutingPolicy and shouldn't retrigger this debounce.
310
307
  useEffect(() => {
311
308
  if (!draft || !routing.data) {
312
309
  setAvailability({});
@@ -325,7 +322,7 @@ export function GatewayRouting({ projectId, canWrite, projectDefaultPending, })
325
322
  const timer = setTimeout(() => {
326
323
  Promise.all(targets.map(async (model) => {
327
324
  try {
328
- const result = await routing.preview.mutateAsync({
325
+ const result = await previewRoute({
329
326
  requestedModel: model,
330
327
  imageInput: false,
331
328
  });
@@ -345,7 +342,7 @@ export function GatewayRouting({ projectId, canWrite, projectDefaultPending, })
345
342
  });
346
343
  }, 400);
347
344
  return () => clearTimeout(timer);
348
- }, [draft, routing.data, modelDefaults.projectDefault]);
345
+ }, [draft, routing.data, modelDefaults.projectDefault, previewRoute]);
349
346
  const models = useMemo(() => {
350
347
  const byWire = new Map();
351
348
  for (const model of catalogModels) {
@@ -441,7 +438,7 @@ export function GatewayRouting({ projectId, canWrite, projectDefaultPending, })
441
438
  };
442
439
  });
443
440
  };
444
- return (_jsxs(RoutingSection, { action: writable ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: controlsDisabled, onClick: () => setResetOpen(true), children: [_jsx(RotateCcw, { className: "size-3.5" }), " ", tI18nComplete.raw('textdaee7606b339')] })) : (_jsx(Badge, { variant: "muted", size: "sm", children: tI18nComplete.raw('text8ac767353080') })), children: [_jsxs("div", { className: "space-y-8", children: [_jsxs("section", { className: "space-y-1", children: [_jsx(Label, { className: "text-foreground", children: tI18nComplete.raw('text325e84939c64') }), _jsxs("div", { className: "flex flex-col", children: [_jsx(RoutingRow, { label: tI18nComplete.raw('text146b38e7fa80'), hint: tI18nComplete.raw('textd48d1b0dbdd6'), children: _jsxs(Select, { value: fallbackMode, disabled: controlsDisabled, onValueChange: (mode) => changeFallbackMode(mode), children: [_jsx(SelectTrigger, { className: "w-48 max-w-full", "aria-label": tI18nComplete.raw('textdf33a10e5579'), children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "inherit", children: tI18nComplete.raw('text975aa2ab538e') }), _jsx(SelectItem, { value: "custom", children: tI18nComplete.raw('texta913ec01bd04') }), _jsx(SelectItem, { value: "disabled", children: tI18nComplete.raw('text3822b0412ed7') })] })] }) }), fallbackMode === 'custom' && draft.defaultFallback ? (_jsx(RoutingRow, { align: "start", label: _jsx("span", { className: "truncate font-mono text-xs", children: primaryModel }), hint: tI18nComplete.raw('textc3ecd3cd791b'), children: _jsxs("div", { className: "space-y-1.5", children: [_jsx(AvailabilityBadge, { available: availability[primaryModel] }), _jsx(ChainRows, { primary: primaryModel, chain: draft.defaultFallback, models: models, disabled: controlsDisabled, availability: availability, onChange: (defaultFallback) => setDraft({ ...draft, defaultFallback }) })] }) })) : fallbackMode === 'inherit' ? (_jsx(RoutingRow, { label: tI18nComplete.raw('text74a1a0f65906'), children: _jsx("p", { className: "text-muted-foreground truncate font-mono text-xs", children: inheritedRoute }) })) : (_jsx(RoutingRow, { label: tI18nComplete.raw('text0af465ef42c0'), children: _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('texta3df3f9e4422') }) }))] })] }), _jsxs("section", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-foreground", children: tI18nComplete.raw('text5c63c15261bb') }), draft.rules.length > 0 ? (_jsx(Badge, { variant: "secondary", size: "sm", children: draft.rules.length })) : null] }), writable ? (_jsxs(Button, { type: "button", size: "sm", variant: "secondary", disabled: controlsDisabled || !newRuleModel || draft.rules.length >= MAX_RULES, onClick: () => newRuleModel &&
441
+ return (_jsxs(RoutingSection, { action: writable ? (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: controlsDisabled, onClick: () => setResetOpen(true), children: [_jsx(RotateCcw, { className: "size-3.5" }), " ", tI18nComplete.raw('textdaee7606b339')] })) : (_jsx(Badge, { variant: "muted", size: "sm", children: tI18nComplete.raw('text8ac767353080') })), children: [_jsxs("div", { className: "space-y-8", children: [_jsxs("section", { className: "space-y-1", children: [_jsx(Label, { className: "text-foreground", children: tI18nComplete.raw('text325e84939c64') }), _jsxs("div", { className: "flex flex-col", children: [_jsx(RoutingRow, { label: tI18nComplete.raw('text146b38e7fa80'), hint: tI18nComplete.raw('textd48d1b0dbdd6'), children: _jsxs(Select, { value: fallbackMode, disabled: controlsDisabled, onValueChange: (mode) => changeFallbackMode(mode), children: [_jsx(SelectTrigger, { className: "w-48 max-w-full", "aria-label": tI18nComplete.raw('textdf33a10e5579'), children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "inherit", children: tI18nComplete.raw('text975aa2ab538e') }), _jsx(SelectItem, { value: "custom", children: tI18nComplete.raw('texta913ec01bd04') }), _jsx(SelectItem, { value: "disabled", children: tI18nComplete.raw('text3822b0412ed7') })] })] }) }), fallbackMode === 'custom' && draft.defaultFallback ? (_jsx(RoutingRow, { align: "start", label: tI18nComplete.raw('texte75d99a42dba'), hint: tI18nComplete.raw('textc3ecd3cd791b'), children: _jsx("div", { className: "space-y-1.5", children: _jsx(ChainRows, { primary: primaryModel, chain: draft.defaultFallback, models: models, disabled: controlsDisabled, availability: availability, onChange: (defaultFallback) => setDraft({ ...draft, defaultFallback }) }) }) })) : fallbackMode === 'inherit' ? (_jsx(RoutingRow, { label: tI18nComplete.raw('text74a1a0f65906'), children: _jsx("p", { className: "text-muted-foreground truncate font-mono text-xs", children: inheritedRoute }) })) : (_jsx(RoutingRow, { label: tI18nComplete.raw('text0af465ef42c0'), children: _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('texta3df3f9e4422') }) }))] })] }), _jsxs("section", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-foreground", children: tI18nComplete.raw('text5c63c15261bb') }), draft.rules.length > 0 ? (_jsx(Badge, { variant: "secondary", size: "sm", children: draft.rules.length })) : null] }), writable ? (_jsxs(Button, { type: "button", size: "sm", variant: "secondary", disabled: controlsDisabled || !newRuleModel || draft.rules.length >= MAX_RULES, onClick: () => newRuleModel &&
445
442
  setDraft({
446
443
  ...draft,
447
444
  rules: [
@@ -7,11 +7,9 @@ export declare function grantSummary(v: AgentGrantSetV2 | undefined, tI18nComple
7
7
  label: string;
8
8
  tone: 'muted' | 'outline';
9
9
  };
10
- export declare function GrantModeField({ value, onChange, allLabel, noneLabel, alwaysRender, children, }: {
10
+ export declare function GrantModeField({ value, onChange, alwaysRender, children, }: {
11
11
  value: AgentGrantSetV2 | undefined;
12
12
  onChange: (v: AgentGrantSetV2) => void;
13
- allLabel: string;
14
- noneLabel: string;
15
13
  /** Render `children` in every mode, not only Pick — for a catalog that
16
14
  * stays on screen and shows each card as included / excluded. */
17
15
  alwaysRender?: boolean;
@@ -34,12 +32,11 @@ export interface GrantOption {
34
32
  /** At the row's right edge — a `Badge` for a connector that needs auth. */
35
33
  trailing?: ReactNode;
36
34
  }
37
- export declare function GrantSetField({ value, onChange, options, emptyLabel, allLabel, rowAccessory, }: {
35
+ export declare function GrantSetField({ value, onChange, options, emptyLabel, rowAccessory, }: {
38
36
  value: AgentGrantSetV2 | undefined;
39
37
  onChange: (v: AgentGrantSetV2) => void;
40
38
  options: GrantOption[];
41
39
  emptyLabel: string;
42
- allLabel: string;
43
40
  /** Optional control rendered BESIDE each granted row (e.g. the connectors
44
41
  * field's "personal" toggle). The row itself is a `<button>`, so an
45
42
  * interactive accessory cannot be nested inside it — when this returns a
@@ -7,9 +7,9 @@ import { useTranslations as useI18nTranslations } from '../../../i18n/index.js';
7
7
  * (kortix_permissions) share the same state transitions instead of re-implementing
8
8
  * them twice.
9
9
  */
10
- import { Tabs, TabsListCompact, TabsTriggerCompact } from '../../../primitives/tabs.js';
11
- import { cn } from '../../../internal/lib/utils.js';
12
- import { CheckIcon } from '@phosphor-icons/react';
10
+ import { Tabs, TabsList, TabsTrigger } from '../../../primitives/tabs.js';
11
+ import { Badge } from '../../../primitives/badge.js';
12
+ import { Checkbox } from '../../../primitives/checkbox.js';
13
13
  import { useState } from 'react';
14
14
  import { KORTIX_PERMISSIONS_CATALOG } from './agent-editor-catalog.js';
15
15
  import { useUiParams } from '../../../providers/ui-provider.js';
@@ -31,7 +31,7 @@ const GRANT_MODES = [
31
31
  { value: 'pick', label: 'Pick' },
32
32
  { value: 'none', label: 'None' },
33
33
  ];
34
- export function GrantModeField({ value, onChange, allLabel, noneLabel, alwaysRender = false, children, }) {
34
+ export function GrantModeField({ value, onChange, alwaysRender = false, children, }) {
35
35
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
36
36
  const mode = value === 'all' ? 'all' : value === 'none' || value === undefined ? 'none' : 'pick';
37
37
  const [wantPick, setWantPick] = useState(() => Array.isArray(value) && value.length > 0);
@@ -57,13 +57,13 @@ export function GrantModeField({ value, onChange, allLabel, noneLabel, alwaysRen
57
57
  next.add(id);
58
58
  onChange([...next]);
59
59
  };
60
- return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Tabs, { value: effectiveMode, onValueChange: (m) => pick(m), className: "w-fit", children: _jsx(TabsListCompact, { type: "default", "aria-label": tI18nComplete.raw('text74009e6664f5'), children: GRANT_MODES.map((m) => (_jsx(TabsTriggerCompact, { value: m.value, children: m.label }, m.value))) }) }), effectiveMode === 'all' && (_jsx("span", { className: "text-muted-foreground text-xs", children: allLabel })), effectiveMode === 'none' && (_jsx("span", { className: "text-muted-foreground text-xs", children: noneLabel }))] }), effectiveMode === 'pick' || alwaysRender
60
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(Tabs, { value: effectiveMode, onValueChange: (m) => pick(m), children: _jsx(TabsList, { "aria-label": tI18nComplete.raw('text74009e6664f5'), children: GRANT_MODES.map((m) => (_jsx(TabsTrigger, { value: m.value, children: m.label }, m.value))) }) }), effectiveMode === 'pick' || alwaysRender
61
61
  ? children({ selected, toggle, mode: effectiveMode })
62
62
  : null] }));
63
63
  }
64
- export function GrantSetField({ value, onChange, options, emptyLabel, allLabel, rowAccessory, }) {
64
+ export function GrantSetField({ value, onChange, options, emptyLabel, rowAccessory, }) {
65
65
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
66
- return (_jsx(GrantModeField, { value: value, onChange: onChange, allLabel: allLabel, noneLabel: tI18nComplete.raw('text765b240c2900'), children: ({ selected, toggle }) => {
66
+ return (_jsx(GrantModeField, { value: value, onChange: onChange, children: ({ selected, toggle }) => {
67
67
  const optionIds = new Set(options.map((o) => o.id));
68
68
  const orphans = [];
69
69
  for (const id of selected) {
@@ -71,14 +71,11 @@ export function GrantSetField({ value, onChange, options, emptyLabel, allLabel,
71
71
  orphans.push({ id, label: id });
72
72
  }
73
73
  const rows = [...options, ...orphans];
74
- return rows.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs", children: emptyLabel })) : (_jsx("div", { className: "border-border/60 max-h-80 overflow-y-auto rounded-md border p-1", children: rows.map((o) => {
74
+ return rows.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs", children: emptyLabel })) : (_jsx("ul", { className: "bg-popover divide-border max-h-96 divide-y overflow-y-auto rounded-md border", children: rows.map((o) => {
75
75
  const isSel = selected.has(o.id);
76
76
  const isOrphan = !optionIds.has(o.id);
77
77
  const accessory = rowAccessory?.(o.id, isSel);
78
- const row = (_jsxs("button", { type: "button", "aria-pressed": isSel, onClick: () => toggle(o.id), className: cn('flex items-center gap-2.5 rounded px-2 text-left text-xs transition-[color,background-color,transform] active:scale-[0.98]', o.description ? 'py-2' : 'py-1.5', accessory ? 'min-w-0 flex-1' : 'w-full', isSel ? 'bg-secondary' : 'hover:bg-muted/50'), children: [_jsx("span", { className: cn('flex size-4 shrink-0 items-center justify-center rounded-[4px] border', isSel
79
- ? 'border-foreground bg-foreground text-background'
80
- : 'border-border/70'), children: isSel ? _jsx(CheckIcon, { className: "size-2.5" }) : null }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-mono", children: o.label }), o.description ? (_jsx("span", { className: "text-muted-foreground block truncate text-xs leading-4", children: o.description })) : null] }), o.trailing ? _jsx("span", { className: "shrink-0", children: o.trailing }) : null, isOrphan && (_jsx("span", { className: "text-kortix-orange shrink-0", children: tI18nComplete.raw('textffa63583dfa6') }))] }, o.id));
81
- return accessory ? (_jsxs("div", { className: "flex items-center gap-1", children: [row, accessory] }, o.id)) : (row);
78
+ return (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Checkbox, { variant: "black", checked: isSel, onCheckedChange: () => toggle(o.id), "aria-label": o.label }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate font-mono text-sm", children: o.label }), o.description ? (_jsx("span", { className: "text-muted-foreground block truncate text-xs", children: o.description })) : null] }), isOrphan ? (_jsx(Badge, { variant: "warning", size: "xs", children: tI18nComplete.raw('textffa63583dfa6') })) : null, o.trailing, accessory] }, o.id));
82
79
  }) }));
83
80
  } }));
84
81
  }
@@ -88,10 +85,5 @@ export function KortixPermissionsField({ value, onChange, }) {
88
85
  const params = useUiParams();
89
86
  const { flags } = useProjectFeatureFlags(typeof params.id === 'string' ? params.id : null);
90
87
  const groups = KORTIX_PERMISSIONS_CATALOG.filter((grp) => !grp.featureFlag || flags[grp.featureFlag] === true);
91
- return (_jsx(GrantModeField, { value: value, onChange: onChange, allLabel: tI18nComplete.raw('text75a5e3a6d5d3'), noneLabel: tI18nComplete.raw('text765b240c2900'), children: ({ selected, toggle }) => (_jsx("div", { className: "border-border/60 max-h-64 space-y-3 overflow-y-auto rounded-md border p-2.5", children: groups.map((grp) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-muted-foreground text-xs font-medium", children: grp.group }), _jsx("div", { className: "flex flex-wrap gap-1", children: grp.actions.map((action) => {
92
- const isSel = selected.has(action);
93
- return (_jsx("button", { type: "button", "aria-pressed": isSel, onClick: () => toggle(action), className: cn('rounded px-1.5 py-1 font-mono text-xs transition-[color,background-color,transform] active:scale-[0.96]', isSel
94
- ? 'bg-foreground text-background'
95
- : 'bg-muted/40 text-muted-foreground hover:bg-muted'), children: action.replace(/^project\./, '') }, action));
96
- }) })] }, grp.group))) })) }));
88
+ return (_jsx(GrantModeField, { value: value, onChange: onChange, children: ({ selected, toggle }) => (_jsx("div", { className: "bg-popover divide-border divide-y rounded-md border", children: groups.map((grp) => (_jsxs("div", { className: "space-y-2.5 px-4 py-3", children: [_jsx("p", { className: "text-muted-foreground text-xs font-medium", children: grp.group }), _jsx("div", { className: "-mx-3 grid gap-x-2 gap-y-0.5 sm:grid-cols-2", children: grp.actions.map((action) => (_jsx(Checkbox, { variant: "black", checked: selected.has(action), onCheckedChange: () => toggle(action), label: _jsx("span", { className: "font-mono text-xs", children: action.replace(/^project\./, '') }) }, action))) })] }, grp.group))) })) }));
97
89
  }
@@ -3,7 +3,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations as useI18nTranslations } from '../../../i18n/index.js';
4
4
  /** The tool-permission editor — a default action plus per-tool rules (a bare
5
5
  * action, or a map of path/command patterns to actions). */
6
- import { Badge } from '../../../primitives/badge.js';
7
6
  import { Button } from '../../../primitives/button.js';
8
7
  import Hint from '../../../primitives/hint.js';
9
8
  import { Input } from '../../../primitives/input.js';
@@ -12,6 +11,8 @@ import { PlusIcon as Plus, TrashIcon as Trash2 } from '@phosphor-icons/react';
12
11
  import { AnimatePresence, m } from 'motion/react';
13
12
  import { useState } from 'react';
14
13
  import { PERMISSION_ACTION_LABEL, PERMISSION_ACTION_ONLY_GROUP_LABEL, PERMISSION_ACTION_ONLY_KEYS, PERMISSION_ACTIONS, PERMISSION_KEY_HELP, PERMISSION_KEY_LABEL, PERMISSION_RULE_GROUPS, PERMISSION_RULE_KEYS, } from './agent-editor-catalog.js';
14
+ import { SettingsRow, SettingsRowGroup } from '../../../primitives/settings-row.js';
15
+ import { SettingsSubsectionHeader } from '../../../primitives/settings-subsection-header.js';
15
16
  import { EditorSection } from './agent-editor-primitives.js';
16
17
  /** Radix forbids `""` as an item value, so the inherit state carries a sentinel. */
17
18
  const INHERIT = '__inherit__';
@@ -41,7 +42,7 @@ function asPermObject(permission) {
41
42
  * leads; the token stays because people grep for it and hand-edit the YAML.
42
43
  */
43
44
  function PermissionKeyName({ permKey }) {
44
- return (_jsx(Hint, { label: PERMISSION_KEY_HELP[permKey] ?? permKey, side: "top", children: _jsxs("span", { className: "flex min-w-0 cursor-default items-baseline gap-2", children: [_jsx("span", { className: "text-foreground truncate text-sm", children: PERMISSION_KEY_LABEL[permKey] ?? permKey }), _jsx("span", { className: "text-muted-foreground/70 shrink-0 font-mono text-xs", children: permKey })] }) }));
45
+ return (_jsx(Hint, { label: PERMISSION_KEY_HELP[permKey] ?? permKey, side: "top", children: _jsxs("span", { className: "flex min-w-0 cursor-default items-baseline gap-2", children: [_jsx("span", { className: "text-foreground truncate text-sm", children: PERMISSION_KEY_LABEL[permKey] ?? permKey }), _jsx("span", { className: "text-muted-foreground shrink-0 font-mono text-xs", children: permKey })] }) }));
45
46
  }
46
47
  /** One action-typed key: a bare allow/ask/deny/inherit, plus expandable
47
48
  * pattern → action rules. */
@@ -68,7 +69,7 @@ function PermissionRuleRow({ label, rule, onChange, }) {
68
69
  next[k === from ? to : k] = v;
69
70
  onChange(next);
70
71
  };
71
- return (_jsxs("div", { className: "space-y-2 px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(PermissionKeyName, { permKey: label }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [_jsx(ActionSelect, { value: isMap ? undefined : bare, onChange: setBare, inheritLabel: tI18nComplete.raw('text3f72f0385768'), label: tI18nComplete('textfaaaefbff336', {
72
+ return (_jsxs("div", { className: "space-y-2 px-4 py-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(PermissionKeyName, { permKey: label }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [_jsx(ActionSelect, { value: isMap ? undefined : bare, onChange: setBare, inheritLabel: tI18nComplete.raw('text3f72f0385768'), label: tI18nComplete('textfaaaefbff336', {
72
73
  value0: PERMISSION_KEY_LABEL[label] ?? label,
73
74
  }) }), _jsxs(Button, { type: "button", variant: isMap || showRules ? 'secondary' : 'outline', size: "sm", className: "gap-1 px-2 text-xs", "aria-expanded": showRules, onClick: () => setShowRules((s) => !s), children: [tI18nComplete.raw('text4228aeb07c41'), ruleCount > 0 ? _jsx("span", { className: "tabular-nums", children: ruleCount }) : null] })] })] }), _jsx(AnimatePresence, { initial: false, children: showRules && (_jsx(m.div, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: 'auto' }, exit: { opacity: 0, height: 0 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "overflow-hidden", children: _jsxs("div", { className: "bg-muted/40 space-y-1.5 rounded-md p-2", children: [ruleCount === 0 ? (_jsx("p", { className: "text-muted-foreground px-1 pt-0.5 text-xs text-pretty", children: tI18nComplete.raw('text8d1833ac5f65') })) : null, Object.entries(map).map(([pattern, action]) => (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Input, { value: pattern, "aria-label": tI18nComplete.raw('text4288ade73ff9'), placeholder: tI18nComplete.raw('textcf134a1de504'), variant: "popover", className: "h-8 flex-1 font-mono text-xs", onChange: (e) => renameRule(pattern, e.target.value) }), _jsx(ActionSelect, { value: action, label: tI18nComplete('text494cb0162c19', {
74
75
  value0: pattern || tI18nComplete.raw('textc6fbcde46fa9'),
@@ -92,9 +93,9 @@ export function ToolsSection({ permission, onChange, }) {
92
93
  : permission && Object.keys(permission).length > 0
93
94
  ? `${Object.keys(permission).length} tool${Object.keys(permission).length === 1 ? '' : 's'} customized`
94
95
  : 'Every tool follows the runtime default';
95
- return (_jsx(EditorSection, { title: tI18nComplete.raw('textea93d6a262ec'), description: tI18nComplete.raw('text765f388e2ce6'), trailing: _jsx(Badge, { variant: "outline", size: "sm", children: summary }), children: _jsx("div", { className: "py-4", children: _jsx(PermissionEditor, { permission: permission, onChange: onChange }) }) }));
96
+ return (_jsx(EditorSection, { title: tI18nComplete.raw('textea93d6a262ec'), description: tI18nComplete.raw('text765f388e2ce6'), lead: true, bare: true, children: _jsx(PermissionEditor, { permission: permission, onChange: onChange, summary: summary }) }));
96
97
  }
97
- function PermissionEditor({ permission, onChange, }) {
98
+ function PermissionEditor({ permission, onChange, summary, }) {
98
99
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
99
100
  const obj = asPermObject(permission);
100
101
  const bareDefault = typeof permission === 'string' ? permission : undefined;
@@ -111,7 +112,9 @@ function PermissionEditor({ permission, onChange, }) {
111
112
  next[key] = value;
112
113
  onChange(Object.keys(next).length ? next : undefined);
113
114
  };
114
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-md border px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-foreground text-sm", children: tI18nComplete.raw('text9b99443d24c5') }), _jsx("p", { className: "text-muted-foreground text-xs", children: bareDefault
115
- ? tI18nComplete.raw('textabba11786cee')
116
- : tI18nComplete.raw('texte640da5d14fd') })] }), _jsx(ActionSelect, { value: bareDefault, onChange: setDefault, inheritLabel: tI18nComplete.raw('text8d2dd4e8c7ca'), label: tI18nComplete.raw('text9b99443d24c5') })] }), PERMISSION_RULE_GROUPS.map((group) => (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-muted-foreground text-xs font-medium", children: group.label }), _jsx("div", { className: "divide-border/60 divide-y rounded-md border", children: group.keys.map((key) => (_jsx(PermissionRuleRow, { label: key, rule: obj[key], onChange: (next) => setKey(key, next) }, key))) })] }, group.label))), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-muted-foreground text-xs font-medium", children: PERMISSION_ACTION_ONLY_GROUP_LABEL }), _jsx("div", { className: "divide-border/60 divide-y rounded-md border", children: PERMISSION_ACTION_ONLY_KEYS.map((key) => (_jsxs("div", { className: "flex items-center justify-between gap-3 px-3 py-2.5", children: [_jsx(PermissionKeyName, { permKey: key }), _jsx(ActionSelect, { value: typeof obj[key] === 'string' ? obj[key] : undefined, onChange: (v) => setKey(key, v), inheritLabel: tI18nComplete.raw('text3f72f0385768'), label: PERMISSION_KEY_LABEL[key] ?? key })] }, key))) })] })] }));
115
+ return (_jsxs("div", { className: "space-y-8", children: [_jsx(SettingsRowGroup, { children: _jsx(SettingsRow, { label: tI18nComplete.raw('text9b99443d24c5'), description: bareDefault
116
+ ? tI18nComplete.raw('textabba11786cee')
117
+ : Object.keys(obj).length > 0
118
+ ? `${summary}.`
119
+ : tI18nComplete.raw('texte640da5d14fd'), children: _jsx(ActionSelect, { value: bareDefault, onChange: setDefault, inheritLabel: tI18nComplete.raw('text8d2dd4e8c7ca'), label: tI18nComplete.raw('text9b99443d24c5') }) }) }), PERMISSION_RULE_GROUPS.map((group) => (_jsxs("section", { className: "space-y-3", children: [_jsx(SettingsSubsectionHeader, { title: group.label }), _jsx("div", { className: "bg-popover divide-border divide-y rounded-md border", children: group.keys.map((key) => (_jsx(PermissionRuleRow, { label: key, rule: obj[key], onChange: (next) => setKey(key, next) }, key))) })] }, group.label))), _jsxs("section", { className: "space-y-3", children: [_jsx(SettingsSubsectionHeader, { title: PERMISSION_ACTION_ONLY_GROUP_LABEL }), _jsx("div", { className: "bg-popover divide-border divide-y rounded-md border", children: PERMISSION_ACTION_ONLY_KEYS.map((key) => (_jsxs("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [_jsx(PermissionKeyName, { permKey: key }), _jsx(ActionSelect, { value: typeof obj[key] === 'string' ? obj[key] : undefined, onChange: (v) => setKey(key, v), inheritLabel: tI18nComplete.raw('text3f72f0385768'), label: PERMISSION_KEY_LABEL[key] ?? key })] }, key))) })] })] }));
117
120
  }
@@ -14,6 +14,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
14
14
  import { Field, FieldDescription, FieldLabel } from '../../../primitives/field.js';
15
15
  import Hint from '../../../primitives/hint.js';
16
16
  import { HoverCard, HoverCardContent, HoverCardTrigger } from '../../../primitives/hover-card.js';
17
+ import { Alert, AlertDescription, AlertTitle } from '../../../primitives/alert.js';
17
18
  import { InfoBanner } from '../../../primitives/info-banner.js';
18
19
  import { Input } from '../../../primitives/input.js';
19
20
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupSearch, InputGroupSearchClear, InputGroupSearchIcon, InputGroupSearchInput, } from '../../../primitives/input-group.js';
@@ -666,7 +667,7 @@ function SecretDialog({ open, onOpenChange, projectId, row, connectors, connecto
666
667
  /* Read-only, because another flow assigned it. Same treatment
667
668
  the git-connection secret always had, now covering the LLM
668
669
  gateway and connector rows it was always true of. */
669
- _jsxs("div", { id: "secret-dialog-delivery", className: "border-border bg-sidebar rounded-md border p-3", children: [_jsx(Badge, { variant: selectedDelivery.tone, size: "sm", children: selectedDelivery.label }), _jsxs("p", { className: "text-muted-foreground mt-2 text-xs text-pretty", children: [selectedDelivery.description, " ", tI18nComplete.raw('text5740535823f9')] })] })) : (_jsxs(Select, { value: exposure, onValueChange: (next) => setPickedExposure(next), disabled: save.isPending, children: [_jsx(SelectTrigger, { id: "secret-dialog-delivery", className: "min-h-10 w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: exposureOptions.map((option) => (_jsx(SelectItem, { value: option.exposure, description: option.description, children: option.label }, option.exposure))) })] })), !usageAssigned && (_jsx(FieldDescription, { children: selectedDelivery.description }))] }), strategy === 'runtime' && (_jsx(InfoBanner, { tone: "warning", title: tI18nComplete.raw('text4fb8319405f3'), children: tI18nComplete.raw('textdb71b63b5778') })), grantNotice && (_jsxs(InfoBanner, { tone: "warning", icon: _jsx(DangerTriangleSolid, { weight: "fill" }), title: grantNotice.title, className: "[&_[data-slot=alert-content]]:min-w-0", children: [_jsx("span", { className: "block text-pretty", children: grantNotice.body }), grantPlan.candidates.length > 0 && (_jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [grantPlan.candidates.length > 1 && (_jsxs(Select, { value: selectedGrantAgent ?? '', onValueChange: setGrantAgent, disabled: grant.isPending, children: [_jsx(SelectTrigger, { "aria-label": tI18nComplete.raw('text33e745179bcb'), className: "h-8 w-48", children: _jsx(SelectValue, { placeholder: tI18nComplete.raw('textb6890bc23a9c') }) }), _jsx(SelectContent, { children: grantPlan.candidates.map((candidate) => (_jsx(SelectItem, { value: candidate.name, description: agentGrantCandidateHint(candidate) ?? undefined, children: candidate.name }, candidate.name))) })] })), _jsxs(Button, { type: "button", size: "sm", onClick: startGrant, disabled: !selectedGrantAgent || grant.isPending, children: [grant.isPending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, agentGrantActionLabel(grantPlan, selectedGrantAgent)] })] })), grantPlan.candidates.length === 0 && (_jsx("span", { className: "text-muted-foreground mt-2 block text-xs text-pretty", children: tI18nComplete.raw('textf23c70160b5b') })), selectedGrantHint && (_jsx("span", { className: "text-muted-foreground mt-2 block text-xs text-pretty", children: selectedGrantHint })), grant.error && (_jsx("span", { className: "text-kortix-red mt-2 block text-xs text-pretty", children: agentGrantErrorMessage(grant.error) })), _jsx("pre", { className: "border-border bg-muted mt-2 overflow-x-auto rounded-sm border p-2 font-mono text-xs leading-relaxed", children: grantManifest })] })), needsHosts && (_jsxs("div", { className: "border-border bg-sidebar space-y-4 rounded-md border p-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: tI18nComplete.raw('textd3297c00f7a2') }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text76f48924ee10') })] }), _jsxs(InfoBanner, { tone: "neutral", title: echoNotice.title, className: "[&_[data-slot=alert-content]]:min-w-0", children: [_jsx("span", { className: "block text-pretty", children: echoNotice.body }), _jsx("pre", { className: "border-border bg-muted mt-2 overflow-x-auto rounded-sm border p-2 font-mono text-xs leading-relaxed", children: echoNotice.probe }), _jsx(Link, { href: echoNotice.docsHref, target: "_blank", rel: "noreferrer", className: "text-muted-foreground hover:text-foreground mt-2 inline-block text-xs underline underline-offset-2", children: echoNotice.docsLabel })] }), _jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: "secret-dialog-hosts", children: tI18nComplete.raw('textbba9af131107') }), _jsx(Textarea, { id: "secret-dialog-hosts", value: hosts, onChange: (event) => setEditedHosts(event.target.value), placeholder: tI18nComplete.raw('text07de8e6e567f'), minHeight: 56, maxHeight: 112, variant: "outline", className: "font-mono text-xs", disabled: save.isPending }), _jsx(FieldDescription, { children: tI18nComplete.raw('text80befc2e0620') })] }), legacyDetail && (_jsxs("div", { className: "border-border bg-popover space-y-2 rounded-md border p-3", children: [_jsx("p", { className: "text-sm font-medium", children: legacyDetail.title }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: legacyDetail.body }), _jsx("dl", { className: "text-muted-foreground space-y-0.5 font-mono text-xs", children: legacyDetail.lines.map((line) => (_jsx("dd", { children: line }, line))) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setLegacyInject(null), disabled: save.isPending, children: tI18nComplete.raw('text4a72652fb7e7') })] }))] })), row?.consumer === 'connector' && (_jsx("div", { className: "border-border bg-sidebar space-y-4 rounded-md border p-3", children: _jsxs(Field, { children: [_jsx(FieldLabel, { children: tI18nComplete.raw('textc3d2e79ebdd0') }), connectorsLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-12 rounded-md" }), _jsx(Skeleton, { className: "h-12 rounded-md" })] })) : connectorOptions.length === 0 ? (_jsx(InfoBanner, { tone: "neutral", title: tI18nComplete.raw('text10798ef769fd'), children: tI18nComplete.raw('text0b834e60371f') })) : (_jsx("div", { className: "bg-popover overflow-hidden rounded-md border", children: connectorOptions.map((option, index) => (_jsxs("label", { className: cn('flex min-h-12 items-center gap-3 px-3 py-2', index > 0 && 'border-t', option.disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'), children: [_jsx(Checkbox, { checked: effectiveSelectedConnectorSlugSet.has(option.slug), disabled: option.disabled || save.isPending, onCheckedChange: (checked) => {
670
+ _jsxs("div", { id: "secret-dialog-delivery", className: "border-border bg-sidebar rounded-md border p-3", children: [_jsx(Badge, { variant: selectedDelivery.tone, size: "sm", children: selectedDelivery.label }), _jsxs("p", { className: "text-muted-foreground mt-2 text-xs text-pretty", children: [selectedDelivery.description, " ", tI18nComplete.raw('text5740535823f9')] })] })) : (_jsxs(Select, { value: exposure, onValueChange: (next) => setPickedExposure(next), disabled: save.isPending, children: [_jsx(SelectTrigger, { id: "secret-dialog-delivery", className: "min-h-10 w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: exposureOptions.map((option) => (_jsx(SelectItem, { value: option.exposure, description: option.description, children: option.label }, option.exposure))) })] })), !usageAssigned && (_jsx(FieldDescription, { children: selectedDelivery.description }))] }), strategy === 'runtime' && (_jsxs(Alert, { variant: "warning", children: [_jsx(AlertTitle, { children: tI18nComplete.raw('text4fb8319405f3') }), _jsx(AlertDescription, { children: tI18nComplete.raw('textdb71b63b5778') })] })), grantNotice && (_jsxs(InfoBanner, { tone: "warning", icon: _jsx(DangerTriangleSolid, { weight: "fill" }), title: grantNotice.title, className: "[&_[data-slot=alert-content]]:min-w-0", children: [_jsx("span", { className: "block text-pretty", children: grantNotice.body }), grantPlan.candidates.length > 0 && (_jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-2", children: [grantPlan.candidates.length > 1 && (_jsxs(Select, { value: selectedGrantAgent ?? '', onValueChange: setGrantAgent, disabled: grant.isPending, children: [_jsx(SelectTrigger, { "aria-label": tI18nComplete.raw('text33e745179bcb'), className: "h-8 w-48", children: _jsx(SelectValue, { placeholder: tI18nComplete.raw('textb6890bc23a9c') }) }), _jsx(SelectContent, { children: grantPlan.candidates.map((candidate) => (_jsx(SelectItem, { value: candidate.name, description: agentGrantCandidateHint(candidate) ?? undefined, children: candidate.name }, candidate.name))) })] })), _jsxs(Button, { type: "button", size: "sm", onClick: startGrant, disabled: !selectedGrantAgent || grant.isPending, children: [grant.isPending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, agentGrantActionLabel(grantPlan, selectedGrantAgent)] })] })), grantPlan.candidates.length === 0 && (_jsx("span", { className: "text-muted-foreground mt-2 block text-xs text-pretty", children: tI18nComplete.raw('textf23c70160b5b') })), selectedGrantHint && (_jsx("span", { className: "text-muted-foreground mt-2 block text-xs text-pretty", children: selectedGrantHint })), grant.error && (_jsx("span", { className: "text-kortix-red mt-2 block text-xs text-pretty", children: agentGrantErrorMessage(grant.error) })), _jsx("pre", { className: "border-border bg-muted mt-2 overflow-x-auto rounded-sm border p-2 font-mono text-xs leading-relaxed", children: grantManifest })] })), needsHosts && (_jsxs("div", { className: "border-border bg-sidebar space-y-4 rounded-md border p-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: tI18nComplete.raw('textd3297c00f7a2') }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text76f48924ee10') })] }), _jsxs(InfoBanner, { tone: "neutral", title: echoNotice.title, className: "[&_[data-slot=alert-content]]:min-w-0", children: [_jsx("span", { className: "block text-pretty", children: echoNotice.body }), _jsx("pre", { className: "border-border bg-muted mt-2 overflow-x-auto rounded-sm border p-2 font-mono text-xs leading-relaxed", children: echoNotice.probe }), _jsx(Link, { href: echoNotice.docsHref, target: "_blank", rel: "noreferrer", className: "text-muted-foreground hover:text-foreground mt-2 inline-block text-xs underline underline-offset-2", children: echoNotice.docsLabel })] }), _jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: "secret-dialog-hosts", children: tI18nComplete.raw('textbba9af131107') }), _jsx(Textarea, { id: "secret-dialog-hosts", value: hosts, onChange: (event) => setEditedHosts(event.target.value), placeholder: tI18nComplete.raw('text07de8e6e567f'), minHeight: 56, maxHeight: 112, variant: "outline", className: "font-mono text-xs", disabled: save.isPending }), _jsx(FieldDescription, { children: tI18nComplete.raw('text80befc2e0620') })] }), legacyDetail && (_jsxs("div", { className: "border-border bg-popover space-y-2 rounded-md border p-3", children: [_jsx("p", { className: "text-sm font-medium", children: legacyDetail.title }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: legacyDetail.body }), _jsx("dl", { className: "text-muted-foreground space-y-0.5 font-mono text-xs", children: legacyDetail.lines.map((line) => (_jsx("dd", { children: line }, line))) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setLegacyInject(null), disabled: save.isPending, children: tI18nComplete.raw('text4a72652fb7e7') })] }))] })), row?.consumer === 'connector' && (_jsx("div", { className: "border-border bg-sidebar space-y-4 rounded-md border p-3", children: _jsxs(Field, { children: [_jsx(FieldLabel, { children: tI18nComplete.raw('textc3d2e79ebdd0') }), connectorsLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-12 rounded-md" }), _jsx(Skeleton, { className: "h-12 rounded-md" })] })) : connectorOptions.length === 0 ? (_jsx(InfoBanner, { tone: "neutral", title: tI18nComplete.raw('text10798ef769fd'), children: tI18nComplete.raw('text0b834e60371f') })) : (_jsx("div", { className: "bg-popover overflow-hidden rounded-md border", children: connectorOptions.map((option, index) => (_jsxs("label", { className: cn('flex min-h-12 items-center gap-3 px-3 py-2', index > 0 && 'border-t', option.disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'), children: [_jsx(Checkbox, { checked: effectiveSelectedConnectorSlugSet.has(option.slug), disabled: option.disabled || save.isPending, onCheckedChange: (checked) => {
670
671
  setSelectedConnectorSlugs((current) => checked
671
672
  ? [
672
673
  ...new Set([
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useContext } from 'react';
3
+ import { createContext, useContext, useMemo } from 'react';
4
4
  import { railGroups } from './rail.js';
5
5
  export const DEFAULT_SETTINGS_RAIL_CHROME_COPY = {
6
6
  settings: 'Settings',
@@ -14,13 +14,15 @@ const DEFAULT_VALUE = {
14
14
  };
15
15
  const SettingsRailCopyContext = createContext(DEFAULT_VALUE);
16
16
  export function SettingsRailCopyProvider({ groups, chrome, children, }) {
17
- return (_jsx(SettingsRailCopyContext.Provider, { value: { groups, chrome }, children: children }));
17
+ const value = useMemo(() => ({ groups, chrome }), [groups, chrome]);
18
+ return (_jsx(SettingsRailCopyContext.Provider, { value: value, children: children }));
18
19
  }
19
20
  export function useSettingsRailChromeCopy() {
20
21
  return useContext(SettingsRailCopyContext).chrome;
21
22
  }
22
23
  export function useSettingsRailItem(tab) {
23
- for (const group of useContext(SettingsRailCopyContext).groups) {
24
+ const { groups } = useContext(SettingsRailCopyContext);
25
+ for (const group of groups) {
24
26
  const item = group.items.find((candidate) => candidate.tab === tab);
25
27
  if (item)
26
28
  return item;
@@ -34,7 +34,7 @@ export interface GeneralTabViewProps {
34
34
  * `useQuery`/mutations, so it can't render under `renderToStaticMarkup`
35
35
  * with no providers. */
36
36
  /** Whether the Delete-workspace section shows at all — gated on
37
- * `project.delete`, NOT the broader `project.write` gate
37
+ * `project.delete`, NOT the broader `project.settings.write` gate
38
38
  * `generalFieldsSlot`'s fields use. Deleting and editing are separate
39
39
  * permissions, so a custom role can grant one without the other. */
40
40
  canDelete?: boolean;
@@ -44,6 +44,13 @@ export interface GeneralTabViewProps {
44
44
  onCloseArchiveDialog?: () => void;
45
45
  onConfirmArchive?: () => void;
46
46
  isArchivePending?: boolean;
47
+ /** `TransferWorkspaceEntry` — the second danger-zone row and its dialog.
48
+ * A slot for the same reason the others are: the dialog owns its own
49
+ * accounts query and mutation, so it cannot render under
50
+ * `renderToStaticMarkup` with no providers. Shares the Delete section's
51
+ * `canDelete` gate — the API's source gate for a transfer is the same
52
+ * destructive tier that gates delete. */
53
+ transferSlot?: ReactNode;
47
54
  }
48
55
  export interface GeneralTabCopy {
49
56
  loadFailed: string;
@@ -64,7 +71,7 @@ export interface GeneralTabCopy {
64
71
  * `ProfileTabView`/`ConnectedAccountsTabView` for the same split. Every
65
72
  * prop is optional with a safe default so the bare `<GeneralTabView />`
66
73
  * the test file renders shows the Delete-workspace section fully formed. */
67
- export declare function GeneralTabView({ copy, isLoading, isError, errorMessage, onRetry, generalFieldsSlot, canDelete, workspaceName, archiveOpen, onOpenArchiveDialog, onCloseArchiveDialog, onConfirmArchive, isArchivePending, }: GeneralTabViewProps): import("react").JSX.Element;
74
+ export declare function GeneralTabView({ copy, isLoading, isError, errorMessage, onRetry, generalFieldsSlot, canDelete, workspaceName, archiveOpen, onOpenArchiveDialog, onCloseArchiveDialog, onConfirmArchive, isArchivePending, transferSlot, }: GeneralTabViewProps): import("react").JSX.Element;
68
75
  export interface RunProjectArchiveClient {
69
76
  archiveProject: (projectId: string) => Promise<unknown>;
70
77
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import { Button } from '../../../primitives/button.js';
5
5
  import { Input } from '../../../primitives/input.js';
6
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../primitives/select.js';
6
7
  import { SettingsRow, SettingsRowGroup } from '../../../primitives/settings-row.js';
7
8
  import { SettingsSubsectionHeader } from '../../../primitives/settings-subsection-header.js';
8
9
  import { Skeleton } from '../../../primitives/skeleton.js';
@@ -19,17 +20,30 @@ import { forgetLastProjectId } from '../../../internal/lib/onboarding/last-proje
19
20
  import { PROJECT_ACTIONS } from '../../../internal/lib/project-actions.js';
20
21
  import { useProjectCans } from '../../../internal/lib/use-project-can.js';
21
22
  import { useSettingsPanelStore } from '../../../internal/stores/settings-panel-store.js';
22
- import { archiveProject, getProject, updateProject, } from '@kortix/sdk';
23
+ import { TransferWorkspaceEntry } from './transfer-workspace-dialog.js';
24
+ import { archiveProject, getProject, setDefaultReasoningEffort, updateProject, } from '@kortix/sdk';
23
25
  import { contract, invalidateProjectIdentity, qk } from '@kortix/sdk/react';
24
26
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
25
27
  import { useUiRouter, useUiViewerId } from '../../../providers/ui-provider.js';
26
28
  import { SettingsTabHeader } from '../settings-tab-header.js';
27
- /** Asked for in ONE batched probe. `project.delete` and `project.write` are
29
+ /** Asked for in ONE batched probe. `project.delete` and `project.settings.write` are
28
30
  * separate permissions — see `ProjectSettingsGeneralProps.canDelete`. */
29
31
  const GENERAL_TAB_ACTIONS = [
30
- PROJECT_ACTIONS.PROJECT_WRITE,
32
+ PROJECT_ACTIONS.PROJECT_SETTINGS_WRITE,
31
33
  PROJECT_ACTIONS.PROJECT_DELETE,
32
34
  ];
35
+ /** The effort ladder the default-effort picker offers. These are models.dev
36
+ * variant ids — technical values, shown capitalized like the composer's
37
+ * own effort selector does (its `label`), never translated.
38
+ *
39
+ * The API clamps the stored default at delivery to what the session's model
40
+ * actually publishes (`effortVariantForPrompt`), so a pick the session's
41
+ * model does not carry degrades to Automatic rather than breaking the turn;
42
+ * the picker still offers the superset the catalog carries. */
43
+ const DEFAULT_EFFORT_OPTIONS = ['none', 'low', 'medium', 'high', 'xhigh', 'max'];
44
+ const DEFAULT_EFFORT_AUTOMATIC = '';
45
+ /** `medium` → `Medium` — same casing rule as the composer's effort selector. */
46
+ const effortOptionLabel = (value) => value.charAt(0).toUpperCase() + value.slice(1);
33
47
  export const DELETE_WORKSPACE_CONSEQUENCES = [
34
48
  'Every session in this workspace, with its files, history, and outputs',
35
49
  'All scheduled runs and triggers — they stop firing straight away',
@@ -61,8 +75,8 @@ const DEFAULT_GENERAL_COPY = {
61
75
  * `ProfileTabView`/`ConnectedAccountsTabView` for the same split. Every
62
76
  * prop is optional with a safe default so the bare `<GeneralTabView />`
63
77
  * the test file renders shows the Delete-workspace section fully formed. */
64
- export function GeneralTabView({ copy = DEFAULT_GENERAL_COPY, isLoading = false, isError = false, errorMessage = '', onRetry = () => { }, generalFieldsSlot, canDelete = true, workspaceName = '', archiveOpen = false, onOpenArchiveDialog = () => { }, onCloseArchiveDialog = () => { }, onConfirmArchive = () => { }, isArchivePending = false, }) {
65
- return (_jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-8", children: [_jsx(SettingsTabHeader, { tab: "general" }), isLoading ? (_jsxs("div", { className: "space-y-5", children: [_jsx(Skeleton, { className: "h-40 rounded-md" }), _jsx(Skeleton, { className: "h-20 rounded-md" })] })) : isError ? (_jsx(ErrorState, { size: "sm", title: copy.loadFailed, description: errorMessage, action: _jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: copy.retry }) })) : (_jsxs(_Fragment, { children: [generalFieldsSlot, canDelete ? (_jsxs("section", { className: "space-y-3", children: [_jsx(SettingsSubsectionHeader, { title: copy.dangerZone }), _jsx(SettingsRowGroup, { children: _jsx(SettingsRow, { label: copy.deleteWorkspace, description: copy.deleteDescription, children: _jsx(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:bg-destructive/10 hover:text-destructive", onClick: onOpenArchiveDialog, children: copy.deleteWorkspace }) }) })] })) : null] })), _jsx(TypeToConfirmDialog, { open: archiveOpen, onOpenChange: (open) => {
78
+ export function GeneralTabView({ copy = DEFAULT_GENERAL_COPY, isLoading = false, isError = false, errorMessage = '', onRetry = () => { }, generalFieldsSlot, canDelete = true, workspaceName = '', archiveOpen = false, onOpenArchiveDialog = () => { }, onCloseArchiveDialog = () => { }, onConfirmArchive = () => { }, isArchivePending = false, transferSlot, }) {
79
+ return (_jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-8", children: [_jsx(SettingsTabHeader, { tab: "general" }), isLoading ? (_jsxs("div", { className: "space-y-5", children: [_jsx(Skeleton, { className: "h-40 rounded-md" }), _jsx(Skeleton, { className: "h-20 rounded-md" })] })) : isError ? (_jsx(ErrorState, { size: "sm", title: copy.loadFailed, description: errorMessage, action: _jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: copy.retry }) })) : (_jsxs(_Fragment, { children: [generalFieldsSlot, canDelete ? (_jsxs("section", { className: "space-y-3", children: [_jsx(SettingsSubsectionHeader, { title: copy.dangerZone }), _jsxs(SettingsRowGroup, { children: [_jsx(SettingsRow, { label: copy.deleteWorkspace, description: copy.deleteDescription, children: _jsx(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:bg-destructive/10 hover:text-destructive", onClick: onOpenArchiveDialog, children: copy.deleteWorkspace }) }), transferSlot] })] })) : null] })), _jsx(TypeToConfirmDialog, { open: archiveOpen, onOpenChange: (open) => {
66
80
  if (!open)
67
81
  onCloseArchiveDialog();
68
82
  }, title: copy.deleteTitle, description: copy.deleteDialog(workspaceName), consequencesTitle: copy.consequencesTitle, consequences: copy.consequences, reassurance: copy.reassurance,
@@ -125,7 +139,7 @@ export function withoutProject(data, projectId) {
125
139
  * `GeneralProjectCard` (name — see this file's header comment for what's
126
140
  * byte-identical) and extended with icon editing, reusing `ProjectIconField`
127
141
  * / `buildProjectEditPatch` unmodified — see this file's header comment. */
128
- function GeneralWorkspaceCard({ project, canManage, }) {
142
+ function GeneralWorkspaceCard({ project, canManage, canManageEffort, }) {
129
143
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
130
144
  const t = useTranslations('settings.workspace');
131
145
  const queryClient = useQueryClient();
@@ -157,6 +171,13 @@ function GeneralWorkspaceCard({ project, canManage, }) {
157
171
  onSettled: () => renameOnSettled(queryClient, project.project_id),
158
172
  });
159
173
  const { mutate: mutateName, isPending: isRenamePending } = renameMutation;
174
+ const effortMutation = useMutation({
175
+ mutationFn: (effort) => setDefaultReasoningEffort(project.project_id, effort),
176
+ onSuccess: (updated) => {
177
+ queryClient.setQueryData(qk.project.summary(project.project_id), updated);
178
+ },
179
+ onError: (error) => errorToast(error.message || t('updateFailed')),
180
+ });
160
181
  useEffect(() => {
161
182
  if (!canManage || isRenamePending)
162
183
  return;
@@ -215,7 +236,7 @@ function GeneralWorkspaceCard({ project, canManage, }) {
215
236
  // Not `variant="popover"` any more: the group it now sits in is
216
237
  // itself `bg-popover`, so a popover-tinted input would vanish into
217
238
  // it. The default `bg-input` is what makes it read as a control.
218
- className: "h-8 w-56" })] })] }));
239
+ className: "h-8 w-56" })] }), _jsx(SettingsRow, { label: t('defaultEffort'), description: t('defaultEffortDescription'), children: _jsxs(Select, { value: project.default_reasoning_effort ?? DEFAULT_EFFORT_AUTOMATIC, onValueChange: (v) => effortMutation.mutate(v === DEFAULT_EFFORT_AUTOMATIC ? null : v), disabled: !canManageEffort || effortMutation.isPending, children: [_jsx(SelectTrigger, { "aria-label": t('defaultEffort'), className: "h-8 w-40 shrink-0", children: _jsx(SelectValue, { placeholder: t('defaultEffortAutomatic') }) }), _jsxs(SelectContent, { align: "end", children: [_jsx(SelectItem, { value: DEFAULT_EFFORT_AUTOMATIC, children: t('defaultEffortAutomatic') }), DEFAULT_EFFORT_OPTIONS.map((effort) => (_jsx(SelectItem, { value: effort, children: effortOptionLabel(effort) }, effort)))] })] }) })] }));
219
240
  }
220
241
  /** Container: owns every hook (React Query, IAM probe, archive dialog state)
221
242
  * and renders `GeneralTabView` with real data + slots. Only ever mounted
@@ -235,12 +256,13 @@ export function GeneralTab({ projectId }) {
235
256
  });
236
257
  const project = projectQuery.data;
237
258
  // Two DIFFERENT permissions, asked for separately. `project.delete` gates the
238
- // Delete-workspace section; `project.write` gates the fields. Reading a
259
+ // Delete-workspace section; `project.settings.write` gates the fields. Reading a
239
260
  // `manager` role label instead would have hidden Delete from a custom role
240
261
  // that holds `project.delete`, and shown it to one that does not.
241
262
  const caps = useProjectCans(projectId, GENERAL_TAB_ACTIONS);
242
263
  const canDelete = caps[PROJECT_ACTIONS.PROJECT_DELETE]?.allowed === true;
243
- const canEdit = caps[PROJECT_ACTIONS.PROJECT_WRITE]?.allowed === true;
264
+ const canEdit = caps[PROJECT_ACTIONS.PROJECT_SETTINGS_WRITE]?.allowed === true;
265
+ const canEditEffort = canEdit;
244
266
  const archiveMutation = useMutation({
245
267
  mutationFn: () => runProjectArchive(projectId, { archiveProject },
246
268
  // The archived project must stop being where `/` and the settings
@@ -283,8 +305,8 @@ export function GeneralTab({ projectId }) {
283
305
  ],
284
306
  reassurance: t('reassurance'),
285
307
  keepWorkspace: t('keepWorkspace'),
286
- }, isLoading: projectQuery.isLoading, isError: projectQuery.isError, errorMessage: projectQuery.error?.message ?? '', onRetry: () => projectQuery.refetch(), generalFieldsSlot: project ? _jsx(GeneralWorkspaceCard, { project: project, canManage: canEdit }) : undefined,
308
+ }, isLoading: projectQuery.isLoading, isError: projectQuery.isError, errorMessage: projectQuery.error?.message ?? '', onRetry: () => projectQuery.refetch(), generalFieldsSlot: project ? _jsx(GeneralWorkspaceCard, { project: project, canManage: canEdit, canManageEffort: canEditEffort }) : undefined,
287
309
  // No git-repo slot: Git repo is its own Settings section since
288
310
  // 2026-09-03 (`project-settings-sections.ts`).
289
- canDelete: canDelete, workspaceName: project?.name, archiveOpen: archiveOpen, onOpenArchiveDialog: () => setArchiveOpen(true), onCloseArchiveDialog: () => setArchiveOpen(false), onConfirmArchive: () => archiveMutation.mutate(), isArchivePending: archiveMutation.isPending }));
311
+ canDelete: canDelete, workspaceName: project?.name, archiveOpen: archiveOpen, onOpenArchiveDialog: () => setArchiveOpen(true), onCloseArchiveDialog: () => setArchiveOpen(false), onConfirmArchive: () => archiveMutation.mutate(), isArchivePending: archiveMutation.isPending, transferSlot: _jsx(TransferWorkspaceEntry, { projectId: projectId, currentAccountId: project?.account_id }) }));
290
312
  }