@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
@@ -66,7 +66,7 @@ import { errorToast, successToast } from '../../primitives/toast.js';
66
66
  import { EmptyState } from '../../primitives/empty-state.js';
67
67
  import { ErrorState } from '../../primitives/error-state.js';
68
68
  import { configEntitySourcePath } from '../sections/component/config-entity-source-path.js';
69
- import { AgentConfigSections, agentConfigSectionKeys, agentEditorOptionQueries, DEFAULT_AGENT_CONFIG_SECTION, isAgentConfigSectionKey, registryGrantOptions, useAgentDraft, useAgentEditorOptions, useLocalizedAgentConfigCatalog, } from '../sections/view/agent-editor.js';
69
+ import { AgentConfigSections, agentConfigSectionKeys, agentEditorOptionQueries, DEFAULT_AGENT_CONFIG_SECTION, grantSummary, isAgentConfigSectionKey, registryGrantOptions, useAgentDraft, useAgentEditorOptions, useLocalizedAgentConfigCatalog, } from '../sections/view/agent-editor.js';
70
70
  import { formatMode, toArray } from '../shared/utils.js';
71
71
  import { editConfigPrompt, useConfigureThread, } from '../use-configure-thread.js';
72
72
  import { agentConfigNeedsUpgrade, useAgentConfig, useUpdateAgentConfig, } from '../../internal/hooks/projects/use-agent-config.js';
@@ -86,11 +86,13 @@ import { FadedScrollArea } from '../../primitives/faded-scroll-area.js';
86
86
  import { SETTINGS_SIDEBAR_WIDTH_PX } from '../../internal/account-hub/account-settings-shell.js';
87
87
  import { capabilityTabHref } from '../../internal/lib/capability-tab-routes.js';
88
88
  import { useIsMobile } from '../../internal/hooks/index.js';
89
- import { EditorSectionStyleProvider } from '../sections/view/agent-editor-primitives.js';
89
+ import { EditorSection, EditorSectionStyleProvider } from '../sections/view/agent-editor-primitives.js';
90
+ import { SettingsSectionHeader } from '../../primitives/settings-section-header.js';
90
91
  import { AgentDrivesSection } from './agent-drives-section.js';
91
92
  import { AgentAuthorityCard } from './agent-authority-card.js';
92
93
  import { AgentModel, AgentScope } from './agent-detail-aside.js';
93
94
  import { AppsGrantPage, ConnectorsGrantPage, SecretsGrantPage, SkillsGrantPage, } from './agent-grant-pages.js';
95
+ import { GroupedUnderlineTabs, GroupSwitchTabs, InlineVerticalTabs, PrototypeSwitcher, useNavVariant, } from './agent-nav.prototype.js';
94
96
  import { AgentPeopleSection } from './agent-people-section.js';
95
97
  import { AgentShareControl } from './agent-share-control.js';
96
98
  import { AgentTriggersSection } from './agent-triggers-section.js';
@@ -162,11 +164,12 @@ export function AgentPage({ projectId, agentName }) {
162
164
  * the one scroller; the rail is the Settings rail
163
165
  * (`project-settings-page.tsx`), row for row.
164
166
  */
165
- function AgentPageFrame({ header, section, sections, sectionHref, pane, footer, }) {
167
+ function AgentPageFrame({ header, section, sections, sectionHref, pane, footer, summaries, }) {
166
168
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
167
169
  const isMobile = useIsMobile();
168
170
  const localizedConfig = useLocalizedAgentConfigCatalog();
169
- const items = localizedConfig.sections.filter((s) => sections.includes(s.key));
171
+ const sectionKeys = new Set(sections);
172
+ const items = localizedConfig.sections.filter((s) => sectionKeys.has(s.key));
170
173
  // The Preferences rail's rule: a heading over a lone group labels nothing.
171
174
  const groups = localizedConfig.groups
172
175
  .map((group) => ({
@@ -183,7 +186,18 @@ function AgentPageFrame({ header, section, sections, sectionHref, pane, footer,
183
186
  // The Settings rail's row dialect (`project-settings-page.tsx`).
184
187
  'gap-2 px-2.5 py-1 font-normal transition-none has-[>svg]:px-2.5', 'text-foreground data-[state=inactive]:text-foreground hover:bg-hover hover:text-foreground', 'data-[state=active]:bg-active data-[state=active]:font-medium', '[&_svg]:text-muted-foreground data-[state=active]:[&_svg]:text-foreground'), children: _jsxs(Link, { href: sectionHref(item.key), prefetch: true, children: [_jsx(SectionIcon, { className: "size-4 shrink-0" }), _jsx("span", { className: cn(!horizontal && 'truncate'), children: item.label })] }) }, item.key));
185
188
  };
186
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "border-border/60 shrink-0 border-b px-5 py-3", children: header }), _jsxs("div", { className: cn('min-h-0 flex-1', isMobile ? 'flex flex-col overflow-hidden' : 'grid overflow-hidden'), style: isMobile ? undefined : { gridTemplateColumns: `${SETTINGS_SIDEBAR_WIDTH_PX}px 1fr` }, children: [isMobile ? (_jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "border-border/60 flex h-auto shrink-0 items-center border-b", children: _jsx(FadedScrollArea, { orientation: "horizontal", fadeColor: "from-background", className: "min-w-0 flex-1 py-2", children: _jsx(Tabs, { value: section, className: "w-fit", children: _jsx(TabsList, { orientation: "horizontal", className: "w-fit", children: items.map((item) => trigger(item, true)) }) }) }) })) : (_jsx("aside", { className: "flex min-h-0 flex-col border-r bg-inherit", children: _jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-4 overflow-y-auto px-2 pt-3 pb-2 [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: _jsx(Tabs, { value: section, orientation: "vertical", className: "space-y-3", children: groups.map((g) => (_jsxs("div", { children: [showGroupLabels ? (_jsx("div", { className: "text-muted-foreground flex h-7 items-center px-2.5 text-xs font-medium", children: g.group })) : null, _jsx(TabsList, { orientation: "vertical", className: "w-full", children: g.items.map((item) => trigger(item, false)) })] }, g.group))) }) }) })), _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-2xl px-6 py-8 pb-24 lg:py-10", children: pane }) })] }), footer] }));
189
+ // PROTOTYPE: `?variant=` swaps the rail for one of the candidate navs.
190
+ const variant = useNavVariant();
191
+ const navProps = {
192
+ groups,
193
+ section,
194
+ href: (key) => sectionHref(key),
195
+ summaries,
196
+ };
197
+ if (!isMobile && variant !== 'rail') {
198
+ return (_jsxs("div", { className: "relative flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: cn('shrink-0 px-5 py-3', variant === 'inline' && 'border-border/60 border-b'), children: header }), variant === 'tabs' ? _jsx(GroupedUnderlineTabs, { ...navProps }) : null, variant === 'groups' ? _jsx(GroupSwitchTabs, { ...navProps }) : null, _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: variant === 'inline' ? (_jsx(InlineVerticalTabs, { ...navProps, children: pane })) : (_jsx("div", { className: "mx-auto w-full max-w-2xl px-4 py-10 pb-32 lg:py-14 lg:pb-32", children: pane })) }), footer, _jsx(PrototypeSwitcher, {})] }));
199
+ }
200
+ return (_jsxs("div", { className: "relative flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "border-border/60 shrink-0 border-b px-5 py-3", children: header }), _jsxs("div", { className: cn('min-h-0 flex-1', isMobile ? 'flex flex-col overflow-hidden' : 'grid overflow-hidden'), style: isMobile ? undefined : { gridTemplateColumns: `${SETTINGS_SIDEBAR_WIDTH_PX}px 1fr` }, children: [isMobile ? (_jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "border-border/60 flex h-auto shrink-0 items-center border-b", children: _jsx(FadedScrollArea, { orientation: "horizontal", fadeColor: "from-background", className: "min-w-0 flex-1 py-2", children: _jsx(Tabs, { value: section, className: "w-fit", children: _jsx(TabsList, { orientation: "horizontal", className: "w-fit", children: items.map((item) => trigger(item, true)) }) }) }) })) : (_jsx("aside", { className: "flex min-h-0 flex-col border-r bg-inherit", children: _jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-4 overflow-y-auto px-2 pt-3 pb-2 [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: _jsx(Tabs, { value: section, orientation: "vertical", className: "space-y-3", children: groups.map((g) => (_jsxs("div", { children: [showGroupLabels ? (_jsx("div", { className: "text-muted-foreground flex h-7 items-center px-2.5 text-xs font-medium", children: g.group })) : null, _jsx(TabsList, { orientation: "vertical", className: "w-full", children: g.items.map((item) => trigger(item, false)) })] }, g.group))) }) }) })), _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-2xl px-4 py-10 pb-32 lg:py-14 lg:pb-32", children: pane }) })] }), footer, _jsx(PrototypeSwitcher, {})] }));
187
201
  }
188
202
  function CenteredState({ children }) {
189
203
  return (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-3xl px-6 py-16", children: children }) }));
@@ -236,7 +250,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
236
250
  : null;
237
251
  const makeDefault = useDefaultAgent(projectId);
238
252
  const pathname = useUiPathname();
239
- return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(AgentShareControl, { projectId: projectId, agentName: agent.name, peopleHref: `${pathname}?section=people` }), startBlocked ? (_jsx(Hint, { label: startBlocked, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { size: "sm", disabled: true, children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] }) }) })) : (_jsxs(Button, { size: "sm", onClick: () => startSession({ create: { agent_name: agent.name } }), children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] })), canWrite ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "icon-sm", "aria-label": tI18nComplete.raw('textf8d46c2570e7'), children: _jsx(DotsThreeIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { disabled: isDefault || agent.enabled === false || makeDefault.isPending, onSelect: () => makeDefault.mutate(agent.name), children: [_jsx(StarIcon, { className: "size-4" }), isDefault
253
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(AgentShareControl, { projectId: projectId, agentName: agent.name, peopleHref: `${pathname}?section=people` }), startBlocked ? (_jsx(Hint, { label: startBlocked, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { size: "sm", disabled: true, children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] }) }) })) : (_jsxs(Button, { size: "sm", onClick: () => startSession({ create: { agent_name: agent.name } }), children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] })), canWrite ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-sm", "aria-label": tI18nComplete.raw('textf8d46c2570e7'), children: _jsx(DotsThreeIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { disabled: isDefault || agent.enabled === false || makeDefault.isPending, onSelect: () => makeDefault.mutate(agent.name), children: [_jsx(StarIcon, { className: "size-4" }), isDefault
240
254
  ? tI18nComplete.raw('texte8cb80e5c5cb')
241
255
  : tI18nComplete.raw('texte8f43f910a4e')] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { disabled: configure.pending, onSelect: () => setConfirmEditSource(true), children: tI18nComplete.raw('text4930821dd07b') })] })] })) : null, _jsx(ConfirmDialog, { open: confirmEditSource, onOpenChange: setConfirmEditSource, title: tI18nComplete.raw('text24ba36a8ec86'), description: tI18nComplete('text13324ae62b23', { value0: agent.name }), confirmLabel: tI18nComplete.raw('text0bce1a892dec'), cancelLabel: tI18nComplete.raw('text19766ed6ccb2'), onConfirm: () => {
242
256
  setConfirmEditSource(false);
@@ -247,7 +261,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
247
261
  function AgentChips({ agent, config, size, }) {
248
262
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
249
263
  const { showMode, isDefault, disabled } = agentStatus(agent, config);
250
- return (_jsxs("span", { className: "flex items-center gap-1.5", children: [showMode ? (_jsx(Badge, { variant: "outline", size: size, className: "text-muted-foreground font-medium", children: formatMode(agent.mode ?? '') })) : null, isDefault ? (_jsxs(Badge, { variant: "outline", size: size, className: "text-muted-foreground gap-1 font-medium", children: [_jsx(StarIcon, { weight: "fill", className: "text-kortix-orange size-3 shrink-0" }), tI18nComplete.raw('text21b111cbfe6e')] })) : null, disabled ? (_jsx(Badge, { variant: "muted", size: size, children: tI18nComplete.raw('text75081b593d15') })) : null] }));
264
+ return (_jsxs("span", { className: "flex items-center gap-1.5", children: [showMode ? (_jsx(Badge, { variant: "secondary", size: size, children: formatMode(agent.mode ?? '') })) : null, isDefault ? (_jsx(Badge, { variant: "secondary", size: size, children: tI18nComplete.raw('text21b111cbfe6e') })) : null, disabled ? (_jsx(Badge, { variant: "muted", size: size, children: tI18nComplete.raw('text75081b593d15') })) : null] }));
251
265
  }
252
266
  /**
253
267
  * Breadcrumb, avatar, name, chips, actions — the full header at the top of
@@ -256,7 +270,7 @@ function AgentChips({ agent, config, size, }) {
256
270
  */
257
271
  function AgentHeader({ projectId, agent, config, canWrite, children, }) {
258
272
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
259
- return (_jsxs("header", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("nav", { "aria-label": tI18nComplete.raw('text2bd873d6c734'), className: "flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: true, className: "text-muted-foreground hover:text-foreground shrink-0 transition-colors", children: tI18nComplete.raw('text279b44d2ab4b') }), _jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground/50 size-3.5 shrink-0" }), _jsx("h1", { className: "text-foreground truncate text-sm font-semibold", children: isMetaAgentName(agent.name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(agent.name) }), _jsx(AgentChips, { agent: agent, config: config, size: "xs" })] }), _jsx(AgentActions, { projectId: projectId, agent: agent, config: config, canWrite: canWrite })] }), children] }));
273
+ return (_jsxs("header", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("nav", { "aria-label": tI18nComplete.raw('text2bd873d6c734'), className: "flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: true, className: "text-muted-foreground hover:text-foreground shrink-0 transition-colors", children: tI18nComplete.raw('text279b44d2ab4b') }), _jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground size-3.5 shrink-0" }), _jsx("h1", { className: "text-foreground truncate text-sm font-semibold", children: isMetaAgentName(agent.name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(agent.name) }), _jsx(AgentChips, { agent: agent, config: config, size: "xs" })] }), _jsx(AgentActions, { projectId: projectId, agent: agent, config: config, canWrite: canWrite })] }), children] }));
260
274
  }
261
275
  // ─── Editable body ─────────────────────────────────────────────────────────
262
276
  /** What a v1 project, or a reader without write, can still see. */
@@ -288,6 +302,22 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
288
302
  const sections = useMemo(() => agentConfigSectionKeys(schemaVersion).filter((k) => drivesOn || k !== 'drives'), [schemaVersion, drivesOn]);
289
303
  const registryOptions = useMemo(() => registryGrantOptions({ manifest_raw: config.manifest_raw, agents: toArray(config.agents) }, agent.name), [config.manifest_raw, config.agents, agent.name]);
290
304
  const section = useAgentSection(sections);
305
+ // PROTOTYPE: the `inline` nav variant shows each topic's current value.
306
+ const summaries = useMemo(() => {
307
+ const d = editor.draft;
308
+ const grant = (v) => grantSummary(v, tI18nComplete).label;
309
+ return {
310
+ skills: grant(d.skills),
311
+ connectors: grant(d.connectors),
312
+ secrets: grant(d.secrets),
313
+ apps: grant(d.apps),
314
+ actions: grant(d.kortix_permissions ?? d.kortix_cli),
315
+ subagents: grant(d.subagents),
316
+ commands: grant(d.commands),
317
+ extensions: grant(d.extensions),
318
+ model: editor.oc.model ? (editor.oc.model.split('/').pop() ?? editor.oc.model) : 'Default',
319
+ };
320
+ }, [editor.draft, editor.oc.model, tI18nComplete]);
291
321
  const onSave = useCallback(async () => {
292
322
  if (!editor.isDirty || update.isPending)
293
323
  return;
@@ -305,7 +335,7 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
305
335
  // shared across topics and nothing is lost.
306
336
  const leaveGuard = useUnsavedChangesGuard(editor.isDirty, pathname);
307
337
  useSaveShortcut(onSave);
308
- return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: true }), section: section, sections: sections, sectionHref: sectionHrefFor(pathname), pane: _jsx(AgentConfigSections, { section: section, editor: editor, options: options, skillsOptions: skillsOptions, registryOptions: registryOptions, skills: _jsx(SkillsGrantPage, { projectId: projectId, config: config, editor: editor }), connectors: _jsx(ConnectorsGrantPage, { projectId: projectId, editor: editor }), secrets: _jsx(SecretsGrantPage, { projectId: projectId, editor: editor }), apps: _jsx(AppsGrantPage, { projectId: projectId, editor: editor }), drives: _jsx(AgentDrivesSection, { projectId: projectId, agentName: agent.name }), overview: _jsx(OverviewPane, { description: editor.oc.description ?? '', onDescriptionChange: (next) => editor.setOc('description', next), descriptionHelp: editor.oc.mode === 'subagent'
338
+ return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: true }), section: section, sections: sections, sectionHref: sectionHrefFor(pathname), summaries: summaries, pane: _jsx(AgentConfigSections, { section: section, editor: editor, options: options, skillsOptions: skillsOptions, registryOptions: registryOptions, skills: _jsx(SkillsGrantPage, { projectId: projectId, config: config, editor: editor }), connectors: _jsx(ConnectorsGrantPage, { projectId: projectId, editor: editor }), secrets: _jsx(SecretsGrantPage, { projectId: projectId, editor: editor }), apps: _jsx(AppsGrantPage, { projectId: projectId, editor: editor }), drives: _jsx(AgentDrivesSection, { projectId: projectId, agentName: agent.name }), overview: _jsx(OverviewPane, { description: editor.oc.description ?? '', onDescriptionChange: (next) => editor.setOc('description', next), descriptionHelp: editor.oc.mode === 'subagent'
309
339
  ? 'Required. This is how other agents decide to call it.'
310
340
  : 'One line on what this agent is for. Other agents read it when picking a subagent.', prompt: editor.oc.prompt ?? '', onPromptChange: (next) => editor.setOc('prompt', next) }), triggers: _jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent }), people: _jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name }), authority: _jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: editor.draft.kortix_permissions ?? editor.draft.kortix_cli }) }), footer: _jsxs(_Fragment, { children: [_jsx(SaveBar, { dirty: editor.isDirty, pending: update.isPending, onDiscard: () => setConfirmDiscard(true), onSave: onSave }), _jsx(ConfirmDialog, { open: confirmDiscard, onOpenChange: setConfirmDiscard, title: tI18nComplete.raw('text3b13192b9d88'), description: tI18nComplete('text8a36cce34f68', { value0: capitalizeWords(agent.name) }), confirmLabel: tI18nComplete.raw('texteb1a70e39274'), cancelLabel: tI18nComplete.raw('texte76fd2add010'), confirmVariant: "destructive", onConfirm: () => {
311
341
  setConfirmDiscard(false);
@@ -317,34 +347,40 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
317
347
  }
318
348
  /**
319
349
  * The Overview topic — what the agent IS: one line of description and the
320
- * instructions, which are the agent. Two cards, in the pane's card dialect.
350
+ * instructions. Laid out like a Settings pane: the pane title, then each
351
+ * field under its own section heading. A field is its own surface; no box
352
+ * wraps a heading and a field together.
321
353
  */
322
354
  function OverviewPane({ description, onDescriptionChange, descriptionHelp, prompt, onPromptChange, }) {
323
355
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
324
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsxs("div", { className: "border-border/60 border-b px-4 pt-4 pb-3", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text526e0087cc3f') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs leading-relaxed text-pretty", children: descriptionHelp })] }), _jsx("div", { className: "px-4 py-4", children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text526e0087cc3f'), value: description, placeholder: tI18nComplete.raw('text446f4eabf99f'), minHeight: 44, className: "text-sm", onChange: (e) => onDescriptionChange(e.target.value) }) })] }), _jsx(InstructionsPanel, { value: prompt, onChange: onPromptChange })] }));
356
+ const tEditor = useI18nTranslations('agentEditor');
357
+ return (_jsxs(_Fragment, { children: [_jsx(SettingsSectionHeader, { title: tEditor('overviewTitle'), className: "pb-1" }), _jsx(EditorSection, { title: tI18nComplete.raw('text526e0087cc3f'), description: descriptionHelp, bare: true, children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text526e0087cc3f'), value: description, placeholder: tI18nComplete.raw('text446f4eabf99f'), minHeight: 44, className: "text-sm", onChange: (e) => onDescriptionChange(e.target.value) }) }), _jsx(InstructionsSection, { value: prompt, onChange: onPromptChange })] }));
325
358
  }
326
359
  /**
327
360
  * The instructions — the agent's system prompt, and the thing that most IS
328
361
  * the agent. Two views of one field: Edit is the textarea, Preview renders
329
362
  * the markdown the way the agent's source file reads. It opens on Preview
330
363
  * when there is something to read and on Edit when there is not; the toggle
331
- * is a segmented pair at the section's right edge, the same control the list
332
- * pages use for their filters.
364
+ * is the segmented `TabsList` at the section heading's right edge.
333
365
  */
334
- function InstructionsPanel({ value, onChange, }) {
366
+ function InstructionsSection({ value, onChange, }) {
335
367
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
336
368
  const [view, setView] = useState(value.trim() ? 'preview' : 'edit');
337
- return (_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsxs("div", { className: "border-border/60 flex items-end justify-between gap-3 border-b px-4 pt-4 pb-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text934652dce41d') }), _jsx("p", { className: "text-muted-foreground text-xs leading-relaxed text-pretty", children: tI18nComplete.raw('text5fe8921b712c') })] }), _jsx(Tabs, { value: view, onValueChange: (next) => setView(next), children: _jsxs(TabsList, { "aria-label": tI18nComplete.raw('texte75bb2d0d554'), children: [_jsx(TabsTrigger, { value: "edit", children: tI18nComplete.raw('text464c4ffd019e') }), _jsx(TabsTrigger, { value: "preview", children: tI18nComplete.raw('text324b134f57c7') })] }) })] }), view === 'edit' ? (_jsx("div", { className: "p-2", children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text934652dce41d'), value: value, placeholder: tI18nComplete.raw('text1436469ad07f'), minHeight: 420, autoFocus: value.trim().length > 0, className: "border-0 font-mono text-xs leading-relaxed focus-visible:ring-0", onChange: (e) => onChange(e.target.value) }) })) : (_jsx("div", { className: "min-h-[420px] px-5 py-4", children: _jsx(MarkdownWithFrontmatter, { content: value }) }))] }));
369
+ return (_jsx(EditorSection, { title: tI18nComplete.raw('text934652dce41d'), description: tI18nComplete.raw('text5fe8921b712c'), trailing: _jsx(Tabs, { value: view, onValueChange: (next) => setView(next), children: _jsxs(TabsList, { "aria-label": tI18nComplete.raw('texte75bb2d0d554'), children: [_jsx(TabsTrigger, { value: "edit", children: tI18nComplete.raw('text464c4ffd019e') }), _jsx(TabsTrigger, { value: "preview", children: tI18nComplete.raw('text324b134f57c7') })] }) }), bare: true, children: view === 'edit' ? (_jsx(Textarea, { "aria-label": tI18nComplete.raw('text934652dce41d'), value: value, placeholder: tI18nComplete.raw('text1436469ad07f'), minHeight: 420, autoFocus: value.trim().length > 0, className: "font-mono text-xs leading-relaxed", onChange: (e) => onChange(e.target.value) })) : (_jsx("div", { className: "bg-popover min-h-[420px] rounded-md border px-5 py-4", children: _jsx(MarkdownWithFrontmatter, { content: value }) })) }));
338
370
  }
339
371
  /**
340
372
  * The save bar. Exists only while there is something to save — an always-on
341
- * disabled pair of buttons is chrome that never earns its row. It sits across
342
- * the foot of the page, outside both scrollers, so it is reachable from any
343
- * point in a long instruction set and from anywhere in the pane.
373
+ * disabled pair of buttons is chrome that never earns its row. It floats at
374
+ * the bottom centre of the page in the Drive selection bar's shape
375
+ * (`project-files/components/drive-selection-bar.tsx`): a `bg-popover` card
376
+ * as wide as its content — the state as a title over one muted line, then
377
+ * Discard and Save as a spaced pair (a confirm is never glued to its cancel).
378
+ * It sits outside both scrollers, so it is reachable
379
+ * from any point in a long instruction set.
344
380
  */
345
381
  function SaveBar({ dirty, pending, onDiscard, onSave, }) {
346
382
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
347
- return (_jsx(AnimatePresence, { initial: false, children: dirty ? (_jsx(m.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 8 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "bg-background/95 border-border/60 z-10 shrink-0 border-t shadow-sm backdrop-blur", children: _jsxs("div", { className: "flex w-full items-center justify-between gap-3 px-6 py-3 lg:px-10", children: [_jsxs("p", { className: "text-muted-foreground min-w-0 truncate text-xs", children: [_jsx("span", { className: "text-foreground font-medium", children: tI18nComplete.raw('textefa5855ff509') }), ' ', tI18nComplete.raw('textc8134fda3694')] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { variant: "outline-ghost", size: "sm", onClick: onDiscard, disabled: pending, children: tI18nComplete.raw('texteb1a70e39274') }), _jsxs(Button, { size: "sm", onClick: onSave, disabled: pending, children: [pending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, tI18nComplete.raw('text1509f561f241'), _jsx(Kbd, { className: "ml-1", children: tI18nComplete.raw('text8cd003e6301b') })] })] })] }) }, "save-bar")) : null }));
383
+ return (_jsx(AnimatePresence, { initial: false, children: dirty ? (_jsx(m.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 8 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "pointer-events-none absolute inset-x-0 bottom-4 z-30 flex justify-center px-4", children: _jsxs("div", { className: "bg-popover text-popover-foreground border-border pointer-events-auto flex max-w-full items-center gap-3 rounded-lg border p-2 pl-3.5 shadow-md", children: [_jsxs("div", { className: "min-w-0", "aria-live": "polite", children: [_jsx("p", { className: "text-foreground truncate text-sm font-medium", children: tI18nComplete.raw('textefa5855ff509') }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: tI18nComplete.raw('textc8134fda3694') })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", onClick: onDiscard, disabled: pending, children: tI18nComplete.raw('texteb1a70e39274') }), _jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: onSave, disabled: pending, children: [pending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, tI18nComplete.raw('text1509f561f241'), _jsx(Kbd, { children: tI18nComplete.raw('text8cd003e6301b') })] })] })] }) }, "save-bar")) : null }));
348
384
  }
349
385
  /**
350
386
  * Mod+S saves. The page is a document you edit, and every editor a person
@@ -444,10 +480,10 @@ function ReadOnlyAgentPage({ projectId, agent, config, canWrite, showUpgradeHint
444
480
  });
445
481
  const pathname = useUiPathname();
446
482
  const section = useAgentSection(isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS);
447
- const source = (_jsxs("section", { className: "bg-popover rounded-md border", children: [_jsxs("div", { className: "border-border/60 border-b px-4 pt-4 pb-3", children: [_jsx("h3", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text934652dce41d') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: _jsx("span", { className: "font-mono", children: sourcePath }) })] }), _jsx(EntityFilePane, { path: sourcePath, content: fileQuery.data?.content, isLoading: fileQuery.isLoading, isError: fileQuery.isError, error: fileQuery.error, onRetry: () => {
483
+ const source = (_jsx(EditorSection, { title: tI18nComplete.raw('text934652dce41d'), description: _jsx("span", { className: "font-mono", children: sourcePath }), lead: true, bare: true, children: _jsx("div", { className: "bg-popover overflow-hidden rounded-md border", children: _jsx(EntityFilePane, { path: sourcePath, content: fileQuery.data?.content, isLoading: fileQuery.isLoading, isError: fileQuery.isError, error: fileQuery.error, onRetry: () => {
448
484
  void fileQuery.refetch();
449
- }, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') })] }));
450
- return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: canWrite && !isPlatform, children: agent.description ? (_jsx("p", { className: "text-muted-foreground max-w-2xl text-sm text-pretty", children: agent.description })) : null }), section: section, sections: isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS, sectionHref: sectionHrefFor(pathname), pane: _jsx(EditorSectionStyleProvider, { value: "panel", children: _jsxs("div", { className: "space-y-4", children: [isPlatform ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('text85b7a59cdbd5'), children: tI18nComplete.raw('textd9c0c74b514b') })) : showUpgradeHint ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('textdc7ab144ca89'), children: tUpgrades.rich('agentEditorHint', {
485
+ }, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') }) }) }));
486
+ return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: canWrite && !isPlatform, children: agent.description ? (_jsx("p", { className: "text-muted-foreground max-w-2xl text-sm text-pretty", children: agent.description })) : null }), section: section, sections: isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS, sectionHref: sectionHrefFor(pathname), pane: _jsx(EditorSectionStyleProvider, { value: "panel", children: _jsxs("div", { className: "space-y-8", children: [isPlatform ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('text85b7a59cdbd5'), children: tI18nComplete.raw('textd9c0c74b514b') })) : showUpgradeHint ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('textdc7ab144ca89'), children: tUpgrades.rich('agentEditorHint', {
451
487
  code: (chunks) => _jsx("span", { className: "font-mono", children: chunks }),
452
- }) })) : null, isPlatform ? null : section === 'overview' ? (_jsxs(_Fragment, { children: [source, _jsx(AgentScope, { projectId: projectId, agentName: agent.name, scope: agent.scope })] })) : section === 'people' ? (_jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name })) : section === 'triggers' ? (_jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent })) : section === 'actions' ? (_jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: agent.scope?.kortix_permissions ?? agent.scope?.kortix_cli })) : (_jsxs("div", { className: "space-y-4", children: [_jsx(AgentModel, { projectId: projectId, agentName: agent.name }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text9dee2a1acfd9') })] }))] }) }) }));
488
+ }) })) : null, isPlatform ? null : section === 'overview' ? (_jsxs(_Fragment, { children: [source, _jsx(AgentScope, { projectId: projectId, agentName: agent.name, scope: agent.scope })] })) : section === 'people' ? (_jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name })) : section === 'triggers' ? (_jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent })) : section === 'actions' ? (_jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: agent.scope?.kortix_permissions ?? agent.scope?.kortix_cli })) : (_jsxs("div", { className: "space-y-3", children: [_jsx(AgentModel, { projectId: projectId, agentName: agent.name }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text9dee2a1acfd9') })] }))] }) }) }));
453
489
  }
@@ -37,7 +37,8 @@ import { EntityAvatar } from '../../primitives/entity-avatar.js';
37
37
  import { Skeleton } from '../../primitives/skeleton.js';
38
38
  import { errorToast, successToast } from '../../primitives/toast.js';
39
39
  import { UserAvatar } from '../../primitives/user-avatar.js';
40
- import { EditorSection } from '../sections/view/agent-editor-primitives.js';
40
+ import { EmptyState } from '../../primitives/empty-state.js';
41
+ import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
41
42
  import { AccessDialog, } from '../../access/access/access-dialog.js';
42
43
  import { builtinRole, customRole, ROLE_NONE } from '../../access/access/role-select.js';
43
44
  import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
@@ -133,20 +134,20 @@ export function AgentPeopleSection({ projectId, agentName, }) {
133
134
  });
134
135
  setEditOpen(true);
135
136
  };
136
- return (_jsxs(EditorSection, { title: tI18nComplete.raw('text82d9af69e1a1'), description: tI18nComplete.raw('text96feb5be077d'), children: [_jsxs("div", { className: "space-y-3 py-3.5", children: [grantsQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-11 w-full rounded-md" }), _jsx(Skeleton, { className: "h-11 w-full rounded-md" })] })) : assigned.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text011c4e48df01') })) : (_jsx("ul", { className: "space-y-2", children: assigned.map((g) => {
137
- const member = g.principal_type === 'member'
138
- ? accessQuery.data?.members.find((m) => m.user_id === g.principal_id)
139
- : undefined;
140
- return (_jsxs("li", { className: "group bg-popover flex items-center gap-3 rounded-md border px-3 py-2", children: [g.principal_type === 'group' ? (_jsx(EntityAvatar, { icon: UsersIcon, size: "md" })) : g.principal_type === 'project' ? (_jsx(EntityAvatar, { icon: UsersThreeIcon, size: "md" })) : (_jsx(UserAvatar, { email: member?.email ?? g.principal_label, size: "md" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: g.principal_type === 'project'
141
- ? tSharing('everyone', { project: g.principal_label })
142
- : g.principal_label }), _jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [g.principal_type === 'project'
143
- ? tSharing('everyoneMeta')
144
- : g.principal_type === 'group'
145
- ? tSharing('groupMeta')
146
- : tSharing('memberMeta'), g.expires_at
147
- ? tI18nComplete('textdc40ace03968', {
148
- value0: new Date(g.expires_at).toLocaleDateString(),
149
- })
150
- : '', g.orphaned ? tI18nComplete.raw('textc795f93da2e5') : ''] })] }), g.principal_type === 'project' ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setStopSharing(g), children: tSharing('removeEveryone') })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "gap-1.5", onClick: () => openEdit(g), disabled: !accountId, children: [_jsx(PencilSimpleIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text464c4ffd019e')] }))] }, g.grant_id));
151
- }) })), accountId ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => setGrantOpen(true), children: [_jsx(PlusIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text8693768c7e08')] })) : null] }), accountId ? (_jsx(AccessDialog, { open: grantOpen, onOpenChange: setGrantOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'grant' }, initialAgentIds: [agentName] })) : null, accountId && editTarget ? (_jsx(AccessDialog, { open: editOpen, onOpenChange: setEditOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'edit', principal: editTarget.principal, current: editTarget.current }, inheritedFrom: editTarget.inheritedFrom }, `${editTarget.principal.type}:${editTarget.principal.id}`)) : null, _jsx(ConfirmDialog, { open: stopSharing !== null, onOpenChange: (open) => !open && setStopSharing(null), title: tSharing('removeEveryoneTitle', { agent: agentName }), description: tSharing('removeEveryoneDescription', { agent: agentName }), confirmLabel: tSharing('removeEveryone'), confirmVariant: "destructive", isPending: stopSharingMutation.isPending, onConfirm: () => stopSharing && stopSharingMutation.mutate(stopSharing.grant_id) })] }));
137
+ return (_jsxs(EditorSection, { title: tI18nComplete.raw('text82d9af69e1a1'), description: tI18nComplete.raw('text96feb5be077d'), trailing: accountId ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setGrantOpen(true), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text8693768c7e08')] })) : undefined, lead: true, bare: true, children: [_jsx("div", { children: grantsQuery.isLoading ? (_jsx(EditorList, { children: [0, 1].map((i) => (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Skeleton, { className: "size-8 rounded-md" }), _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" })] }, i))) })) : assigned.length === 0 ? (_jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('text011c4e48df01') })) : (_jsx(EditorList, { children: assigned.map((g) => {
138
+ const member = g.principal_type === 'member'
139
+ ? accessQuery.data?.members.find((m) => m.user_id === g.principal_id)
140
+ : undefined;
141
+ return (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [g.principal_type === 'group' ? (_jsx(EntityAvatar, { icon: UsersIcon, size: "md" })) : g.principal_type === 'project' ? (_jsx(EntityAvatar, { icon: UsersThreeIcon, size: "md" })) : (_jsx(UserAvatar, { email: member?.email ?? g.principal_label, size: "md" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: g.principal_type === 'project'
142
+ ? tSharing('everyone', { project: g.principal_label })
143
+ : g.principal_label }), _jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [g.principal_type === 'project'
144
+ ? tSharing('everyoneMeta')
145
+ : g.principal_type === 'group'
146
+ ? tSharing('groupMeta')
147
+ : tSharing('memberMeta'), g.expires_at
148
+ ? tI18nComplete('textdc40ace03968', {
149
+ value0: new Date(g.expires_at).toLocaleDateString(),
150
+ })
151
+ : '', g.orphaned ? tI18nComplete.raw('textc795f93da2e5') : ''] })] }), g.principal_type === 'project' ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setStopSharing(g), children: tSharing('removeEveryone') })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "gap-1.5", onClick: () => openEdit(g), disabled: !accountId, children: [_jsx(PencilSimpleIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text464c4ffd019e')] }))] }, g.grant_id));
152
+ }) })) }), accountId ? (_jsx(AccessDialog, { open: grantOpen, onOpenChange: setGrantOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'grant' }, initialAgentIds: [agentName] })) : null, accountId && editTarget ? (_jsx(AccessDialog, { open: editOpen, onOpenChange: setEditOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'edit', principal: editTarget.principal, current: editTarget.current }, inheritedFrom: editTarget.inheritedFrom }, `${editTarget.principal.type}:${editTarget.principal.id}`)) : null, _jsx(ConfirmDialog, { open: stopSharing !== null, onOpenChange: (open) => !open && setStopSharing(null), title: tSharing('removeEveryoneTitle', { agent: agentName }), description: tSharing('removeEveryoneDescription', { agent: agentName }), confirmLabel: tSharing('removeEveryone'), confirmVariant: "destructive", isPending: stopSharingMutation.isPending, onConfirm: () => stopSharing && stopSharingMutation.mutate(stopSharing.grant_id) })] }));
152
153
  }
@@ -28,7 +28,8 @@ import { Badge } from '../../primitives/badge.js';
28
28
  import { Button } from '../../primitives/button.js';
29
29
  import { ConfirmDialog } from '../../primitives/confirm-dialog.js';
30
30
  import { Skeleton } from '../../primitives/skeleton.js';
31
- import { EditorSection } from '../sections/view/agent-editor-primitives.js';
31
+ import { EmptyState } from '../../primitives/empty-state.js';
32
+ import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
32
33
  import { useProjectFeatureFlags } from '../../internal/lib/use-project-feature-flags.js';
33
34
  import { cn } from '../../internal/lib/utils.js';
34
35
  import { listProjectTriggers } from '@kortix/sdk';
@@ -89,10 +90,10 @@ export function AgentTriggersSection({ projectId, agentName, defaultAgent, }) {
89
90
  setSelectedSlug(null);
90
91
  },
91
92
  });
92
- return (_jsxs(EditorSection, { title: tI18nComplete.raw('texte62f2148a64d'), description: tI18nComplete.raw('text1a1e4d549e85'), children: [_jsxs("div", { className: "space-y-3 py-3.5", children: [triggersQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-11 w-full rounded-md" }), _jsx(Skeleton, { className: "h-11 w-full rounded-md" })] })) : mine.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('textb254c6e02e1a') })) : (_jsx("ul", { className: "space-y-2", children: mine.map((trigger) => {
93
- const KindIcon = KIND_ICON[trigger.type] ?? TimerIcon;
94
- return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => setSelectedSlug(trigger.slug), className: cn('group bg-popover flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left', 'hover:border-border hover:bg-accent transition-[background-color,border-color]', 'focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none'), children: [_jsx("span", { className: cn('flex size-8 shrink-0 items-center justify-center rounded-sm', trigger.enabled ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(KindIcon, { weight: "fill", className: cn('size-4', trigger.enabled ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: triggerName(trigger) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: describeWhen(trigger) })] }), !trigger.enabled ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('texte159b06187d3') })) : null] }) }, trigger.slug));
95
- }) })), canCreate ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => setCreateOpen(true), children: [_jsx(PlusIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text58fd308928dd')] })) : null] }), canCreate ? (_jsx(TriggerComposer, { projectId: projectId, open: createOpen, onOpenChange: setCreateOpen, initialAgent: agentName, onCreated: (slug) => {
93
+ return (_jsxs(EditorSection, { title: tI18nComplete.raw('texte62f2148a64d'), description: tI18nComplete.raw('text1a1e4d549e85'), trailing: canCreate ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setCreateOpen(true), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text58fd308928dd')] })) : undefined, lead: true, bare: true, children: [_jsx("div", { children: triggersQuery.isLoading ? (_jsx(EditorList, { children: [0, 1].map((i) => (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Skeleton, { className: "size-8 rounded-sm" }), _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" })] }, i))) })) : mine.length === 0 ? (_jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('textb254c6e02e1a') })) : (_jsx(EditorList, { children: mine.map((trigger) => {
94
+ const KindIcon = KIND_ICON[trigger.type] ?? TimerIcon;
95
+ return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => setSelectedSlug(trigger.slug), className: cn('hover:bg-hover flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors duration-fast', 'focus-visible:bg-hover focus-visible:outline-none'), children: [_jsx("span", { className: cn('flex size-8 shrink-0 items-center justify-center rounded-sm', trigger.enabled ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(KindIcon, { weight: "fill", className: cn('size-4', trigger.enabled ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: triggerName(trigger) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: describeWhen(trigger) })] }), !trigger.enabled ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('texte159b06187d3') })) : null] }) }, trigger.slug));
96
+ }) })) }), canCreate ? (_jsx(TriggerComposer, { projectId: projectId, open: createOpen, onOpenChange: setCreateOpen, initialAgent: agentName, onCreated: (slug) => {
96
97
  setCreateOpen(false);
97
98
  void invalidate();
98
99
  // The composer toasts the creation; straight into the new entry, as the Triggers tab does.
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { customizePath, uiRoutes } from '../../lib/routes.js';
4
4
  import { NewEntityMenu } from '../shared/new-entity-menu.js';
5
5
  import { useTranslations as useI18nTranslations } from '../../i18n/index.js';
6
+ import { agentAccess } from './agent-access.js';
6
7
  import { useMemo, useRef, useState } from 'react';
7
8
  import { Badge } from '../../primitives/badge.js';
8
9
  import { Button } from '../../primitives/button.js';
@@ -163,10 +164,26 @@ function CardCaret() {
163
164
  function AgentCardFacts({ agent, triggerCount, peopleCount, }) {
164
165
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
165
166
  const model = agent.model ? agent.model.split('/').pop() : null;
167
+ const accessLabel = accessLabelOf(agentAccess(agent.scope), tI18nComplete);
166
168
  const sep = (_jsx("span", { "aria-hidden": true, className: "text-muted-foreground", children: "\u00B7" }));
167
169
  return (_jsxs(_Fragment, { children: [_jsx("span", { className: cn('truncate', model && 'font-mono'), children: model ?? tI18nComplete.raw('text3840d9d29421') }), sep, _jsxs("span", { className: "tabular-nums", children: [triggerCount, " ", triggerCount === 1 ? 'trigger' : 'triggers'] }), peopleCount !== null ? (_jsxs(_Fragment, { children: [sep, _jsx("span", { className: "tabular-nums", children: peopleCount === 0
168
170
  ? tI18nComplete.raw('textb6c70926c1aa')
169
- : `${peopleCount} ${peopleCount === 1 ? 'grant' : 'grants'}` })] })) : null] }));
171
+ : `${peopleCount} ${peopleCount === 1 ? 'grant' : 'grants'}` })] })) : null, accessLabel ? _jsx("span", { className: "basis-full tabular-nums", children: accessLabel }) : null] }));
172
+ }
173
+ /** The access slot: what the configuration grants (KRTX-3025). */
174
+ function accessLabelOf(access, t) {
175
+ if (!access)
176
+ return null;
177
+ if (access.kind === 'full')
178
+ return t.raw('textf19611c61ca5');
179
+ const LABELS = {
180
+ connectors: { all: 'textd83250185d41', count: 'text15589a542412' },
181
+ secrets: { all: 'text0b38e3daeb93', count: 'texte96d99ad9896' },
182
+ actions: { all: 'text70b8d7e10749', count: 'text2d3f4433505a' },
183
+ };
184
+ return access.parts
185
+ .map(({ kind, value }) => value === 'all' ? t.raw(LABELS[kind].all) : t(LABELS[kind].count, { count: value }))
186
+ .join(', ');
170
187
  }
171
188
  /**
172
189
  * Card badges — "also a subagent", the default-agent star, disabled.
@@ -25,8 +25,9 @@ export function mcpServerIdentity(rawUrl, takenSlugs) {
25
25
  labels.pop();
26
26
  const base = [...labels].reverse().find((label) => !GENERIC_LABELS.has(label)) ?? labels[labels.length - 1] ?? 'mcp';
27
27
  const root = normalizeConnectorConnectionSlug(base) || 'mcp';
28
+ const taken = new Set(takenSlugs);
28
29
  let slug = root;
29
- for (let n = 2; takenSlugs.includes(slug); n++)
30
+ for (let n = 2; taken.has(slug); n++)
30
31
  slug = `${root}-${n}`;
31
32
  const name = root.charAt(0).toUpperCase() + root.slice(1);
32
33
  return { slug, name };
@@ -37,8 +38,9 @@ export function mcpServerIdentity(rawUrl, takenSlugs) {
37
38
  */
38
39
  export function mcpAppIdentity(appName, takenSlugs) {
39
40
  const root = normalizeConnectorConnectionSlug(appName) || 'mcp';
41
+ const taken = new Set(takenSlugs);
40
42
  let slug = root;
41
- for (let n = 2; takenSlugs.includes(slug); n++)
43
+ for (let n = 2; taken.has(slug); n++)
42
44
  slug = `${root}-${n}`;
43
45
  return { slug, name: appName };
44
46
  }
@@ -46,6 +46,20 @@ export type CatalogEntry = (CatalogEntryFields & {
46
46
  });
47
47
  /** Native platform provider. Each member's paired machine is one of its accounts. */
48
48
  export declare function computersCatalogEntry(tI18nComplete: UiTranslator): CatalogEntry;
49
+ /**
50
+ * The icon URL a Discover record's card loads.
51
+ *
52
+ * A share of the catalogue's records carry a `domain` and no `icon` — the
53
+ * upstream feed only holds a logo for entries it could scrape one for. The
54
+ * card for such a record used to fall straight to a generic globe glyph, which
55
+ * is exactly the "inconsistent or missing imagery" a connector catalogue
56
+ * should not show when the app's own favicon is one well-known URL away. The
57
+ * same favicon service the marketing connectors page and `FaviconAvatar`
58
+ * already use resolves it; where the domain is a pseudo-host (`*.local`, the
59
+ * apis.guru feed's placeholders) it answers the generic globe image, which
60
+ * reads the same as the glyph it replaced.
61
+ */
62
+ export declare function discoverEntryIcon(connector: Pick<DiscoverConnector, 'icon' | 'domain'>): string | null;
49
63
  export declare function catalogEntryFromDiscover(connector: DiscoverConnector): CatalogEntry;
50
64
  export declare function catalogEntryFromEasyConnect(app: PipedreamApp & {
51
65
  provider?: 'composio' | 'pipedream';
@@ -1,3 +1,4 @@
1
+ import { faviconUrlForHostname } from '../../../lib/favicon.js';
1
2
  import { POPULAR_SECTION } from './connector-categories.js';
2
3
  /** Native platform provider. Each member's paired machine is one of its accounts. */
3
4
  export function computersCatalogEntry(tI18nComplete) {
@@ -12,6 +13,22 @@ export function computersCatalogEntry(tI18nComplete) {
12
13
  popularity: null,
13
14
  };
14
15
  }
16
+ /**
17
+ * The icon URL a Discover record's card loads.
18
+ *
19
+ * A share of the catalogue's records carry a `domain` and no `icon` — the
20
+ * upstream feed only holds a logo for entries it could scrape one for. The
21
+ * card for such a record used to fall straight to a generic globe glyph, which
22
+ * is exactly the "inconsistent or missing imagery" a connector catalogue
23
+ * should not show when the app's own favicon is one well-known URL away. The
24
+ * same favicon service the marketing connectors page and `FaviconAvatar`
25
+ * already use resolves it; where the domain is a pseudo-host (`*.local`, the
26
+ * apis.guru feed's placeholders) it answers the generic globe image, which
27
+ * reads the same as the glyph it replaced.
28
+ */
29
+ export function discoverEntryIcon(connector) {
30
+ return connector.icon ?? faviconUrlForHostname(connector.domain);
31
+ }
15
32
  export function catalogEntryFromDiscover(connector) {
16
33
  return {
17
34
  source: 'discover',
@@ -20,7 +37,7 @@ export function catalogEntryFromDiscover(connector) {
20
37
  slug: connector.slug,
21
38
  name: connector.name,
22
39
  description: connector.description,
23
- icon: connector.icon,
40
+ icon: discoverEntryIcon(connector),
24
41
  categories: connector.categories,
25
42
  popularity: connector.popularity,
26
43
  };
@@ -10,6 +10,34 @@ export interface CatalogInstall {
10
10
  pendingKey: string | null;
11
11
  onInstall: (entry: CatalogEntry) => void;
12
12
  }
13
+ /**
14
+ * A catalog favicon, or a neutral glyph tile when the record has none or its
15
+ * URL fails to load.
16
+ *
17
+ * A plain `<img>`, not `next/image`. These are third-party favicons on
18
+ * arbitrary hosts, so the loader was already bypassed with `unoptimized` — which
19
+ * left `fill` costing an absolutely-positioned child inside a `relative`
20
+ * wrapper, per card, for no optimisation in return. Native `loading="lazy"`
21
+ * defers every icon below the fold, which is most of them on a browse page.
22
+ *
23
+ * `width`/`height` are set so the box is reserved before the image arrives and
24
+ * the grid never reflows around a late favicon.
25
+ *
26
+ * The broken-image state is the fallback the null case already had: a logo
27
+ * URL that 404s used to render the browser's broken-image glyph inside the
28
+ * tile, which is worse than no imagery at all. `onError` swaps to the same
29
+ * glyph tile — the same fallback a record with no icon gets, so "missing" and
30
+ * "unavailable" look identical. Mirrors `ConnectorAppIcon`'s broken state.
31
+ *
32
+ * The latch is per URL (`failedUrl`, not a boolean): a URL that failed stays
33
+ * failed, while a swapped URL gets a fresh try without any call-site key —
34
+ * merge-search swaps a merged twin's icon to the managed logo as catalogue
35
+ * pages stream in.
36
+ */
37
+ export declare function ConnectorIcon({ icon, computer, }: {
38
+ icon: string | null;
39
+ computer?: boolean;
40
+ }): import("react").JSX.Element;
13
41
  /**
14
42
  * The catalogue body: one flat, paginated, searchable grid.
15
43
  *
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations } from '../../../i18n/index.js';
4
4
  import { GlobeIcon, MonitorIcon } from '@phosphor-icons/react';
5
- import { memo } from 'react';
5
+ import { memo, useState } from 'react';
6
6
  import { Badge } from '../../../primitives/badge.js';
7
7
  import { Button } from '../../../primitives/button.js';
8
8
  import Loading from '../../../primitives/loading.js';
@@ -43,7 +43,8 @@ function CatalogAffordance({ entry, connected, install, }) {
43
43
  disabled: !install.ready || install.pendingKey !== null, "data-testid": "catalog-add" }));
44
44
  }
45
45
  /**
46
- * A catalog favicon, or a neutral glyph tile when the record has none.
46
+ * A catalog favicon, or a neutral glyph tile when the record has none or its
47
+ * URL fails to load.
47
48
  *
48
49
  * A plain `<img>`, not `next/image`. These are third-party favicons on
49
50
  * arbitrary hosts, so the loader was already bypassed with `unoptimized` — which
@@ -53,12 +54,24 @@ function CatalogAffordance({ entry, connected, install, }) {
53
54
  *
54
55
  * `width`/`height` are set so the box is reserved before the image arrives and
55
56
  * the grid never reflows around a late favicon.
57
+ *
58
+ * The broken-image state is the fallback the null case already had: a logo
59
+ * URL that 404s used to render the browser's broken-image glyph inside the
60
+ * tile, which is worse than no imagery at all. `onError` swaps to the same
61
+ * glyph tile — the same fallback a record with no icon gets, so "missing" and
62
+ * "unavailable" look identical. Mirrors `ConnectorAppIcon`'s broken state.
63
+ *
64
+ * The latch is per URL (`failedUrl`, not a boolean): a URL that failed stays
65
+ * failed, while a swapped URL gets a fresh try without any call-site key —
66
+ * merge-search swaps a merged twin's icon to the managed logo as catalogue
67
+ * pages stream in.
56
68
  */
57
- function ConnectorIcon({ icon, computer = false }) {
58
- if (!icon) {
69
+ export function ConnectorIcon({ icon, computer = false, }) {
70
+ const [failedUrl, setFailedUrl] = useState(null);
71
+ if (!icon || failedUrl === icon) {
59
72
  return (_jsx("span", { className: "bg-card flex size-9 shrink-0 items-center justify-center rounded-sm", children: computer ? _jsx(MonitorIcon, { className: "size-5" }) : _jsx(GlobeIcon, { className: "size-5" }) }));
60
73
  }
61
- return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", className: "size-9 object-contain" }) }));
74
+ return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", className: "size-9 object-contain", onError: () => setFailedUrl(icon) }) }));
62
75
  }
63
76
  /**
64
77
  * One catalogue card: icon and title on the bare page — no border, no
@@ -18,7 +18,9 @@ export type AppRef = {
18
18
  slug: string;
19
19
  };
20
20
  export declare function connectorsHref(projectId: string, scope?: 'all' | 'connected' | 'channels'): string;
21
- /** `null` for the computer entry: a computer has no app page. */
21
+ export declare function connectorSearchFromParams(search: Pick<URLSearchParams, 'get'> | null | undefined): string;
22
+ /** Sets `?q=` to the trimmed query, or removes it when the query is empty. */
23
+ export declare function withConnectorSearch(params: URLSearchParams, query: string): URLSearchParams;
22
24
  export declare function appRefFromEntry(entry: CatalogEntry): AppRef | null;
23
25
  export declare function appHref(projectId: string, app: AppRef): string;
24
26
  export declare function appRefFromLocation(appSegment: string, search: URLSearchParams | null): AppRef | null;
@@ -23,6 +23,24 @@ export function connectorsHref(projectId, scope) {
23
23
  return scope ? `${base(projectId)}?scope=${scope}` : base(projectId);
24
24
  }
25
25
  /** `null` for the computer entry: a computer has no app page. */
26
+ /**
27
+ * The catalogue search, held in `?q=`. Opening a connector is a route change
28
+ * that unmounts the catalogue; a search kept only in state was gone on browser
29
+ * Back (KRTX-3018).
30
+ */
31
+ const SEARCH_PARAM = 'q';
32
+ export function connectorSearchFromParams(search) {
33
+ return search?.get(SEARCH_PARAM)?.trim() ?? '';
34
+ }
35
+ /** Sets `?q=` to the trimmed query, or removes it when the query is empty. */
36
+ export function withConnectorSearch(params, query) {
37
+ const trimmed = query.trim();
38
+ if (trimmed)
39
+ params.set(SEARCH_PARAM, trimmed);
40
+ else
41
+ params.delete(SEARCH_PARAM);
42
+ return params;
43
+ }
26
44
  export function appRefFromEntry(entry) {
27
45
  if (entry.source === 'discover') {
28
46
  return { source: 'discover', slug: entry.slug, id: entry.connector.id };