@kortix/ui 0.14.6 → 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 (123) hide show
  1. package/dist/_vendor/kortix-shared.js +489 -381
  2. package/dist/customize/agents/agent-authority-card.js +1 -2
  3. package/dist/customize/agents/agent-detail-aside.js +15 -14
  4. package/dist/customize/agents/agent-drives-section.js +5 -6
  5. package/dist/customize/agents/agent-grant-pages.js +68 -70
  6. package/dist/customize/agents/agent-nav.prototype.d.ts +35 -0
  7. package/dist/customize/agents/agent-nav.prototype.js +95 -0
  8. package/dist/customize/agents/agent-page.js +57 -22
  9. package/dist/customize/agents/agent-people-section.js +18 -17
  10. package/dist/customize/agents/agent-triggers-section.js +6 -5
  11. package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
  12. package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
  13. package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
  14. package/dist/customize/connectors/catalog/connector-browse.js +18 -5
  15. package/dist/customize/connectors/detail/app-page.js +19 -5
  16. package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
  17. package/dist/customize/connectors/detail/connector-modal.js +18 -101
  18. package/dist/customize/connectors/detail/connector-notice.js +6 -2
  19. package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
  20. package/dist/customize/connectors/detail/connector-page.js +26 -13
  21. package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
  22. package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
  23. package/dist/customize/sections/llm-provider/types.d.ts +3 -0
  24. package/dist/customize/sections/llm-provider/utils.js +2 -2
  25. package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
  26. package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
  27. package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
  28. package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
  29. package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
  30. package/dist/customize/sections/view/agent-editor.js +3 -3
  31. package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
  32. package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
  33. package/dist/customize/sections/view/grant-mode-field.js +10 -18
  34. package/dist/customize/sections/view/permission-editor.js +11 -8
  35. package/dist/customize/sections/view/secrets-view.js +2 -1
  36. package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
  37. package/dist/customize/settings/tabs/general-tab.js +33 -11
  38. package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
  39. package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
  40. package/dist/files/project/api/runtime-files.js +3 -2
  41. package/dist/files/project/file-source.js +18 -1
  42. package/dist/files/project/hooks/use-file-content.js +2 -0
  43. package/dist/files/viewer/file-header.js +1 -1
  44. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  45. package/dist/files/viewer/file-preview-modal.js +12 -2
  46. package/dist/files/viewer/file-source.d.ts +3 -1
  47. package/dist/files/viewer/use-file-content-state.js +26 -7
  48. package/dist/files/viewer/viewer-actions.js +2 -2
  49. package/dist/i18n/en.d.ts +345 -12
  50. package/dist/i18n/en.js +350 -17
  51. package/dist/internal/files/types.d.ts +2 -0
  52. package/dist/internal/goals/goal-meta.d.ts +30 -0
  53. package/dist/internal/goals/goal-meta.js +55 -0
  54. package/dist/internal/lib/query-client-singleton.d.ts +9 -3
  55. package/dist/internal/lib/query-client-singleton.js +18 -12
  56. package/dist/internal/share/session-shares-modal.js +3 -7
  57. package/dist/internal/stores/kortix-computer-store.js +14 -2
  58. package/dist/internal/stores/session-hover-store.d.ts +32 -0
  59. package/dist/internal/stores/session-hover-store.js +38 -0
  60. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  61. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  62. package/dist/lib/routes.d.ts +2 -0
  63. package/dist/lib/routes.js +1 -0
  64. package/dist/primitives/settings-section-header.d.ts +1 -1
  65. package/dist/session/composer/attachment-tile.d.ts +0 -2
  66. package/dist/session/composer/attachment-tile.js +0 -8
  67. package/dist/session/composer/clipboard-files.d.ts +2 -1
  68. package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
  69. package/dist/session/composer/composer-draft-recovery.js +0 -13
  70. package/dist/session/composer/context-ring.d.ts +1 -1
  71. package/dist/session/composer/context-ring.js +2 -2
  72. package/dist/session/composer/context-usage.d.ts +3 -6
  73. package/dist/session/composer/context-usage.js +3 -6
  74. package/dist/session/composer/inbox-row-claims.d.ts +2 -2
  75. package/dist/session/composer/inbox-row-claims.js +1 -1
  76. package/dist/session/composer/queue-projection.d.ts +3 -2
  77. package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
  78. package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
  79. package/dist/session/composer/session-composer-readiness.d.ts +7 -22
  80. package/dist/session/composer/session-composer-readiness.js +12 -40
  81. package/dist/session/composer/token-progress.d.ts +1 -1
  82. package/dist/session/composer/token-progress.js +2 -2
  83. package/dist/session/header/session-goal-indicator.d.ts +1 -0
  84. package/dist/session/header/session-goal-indicator.js +41 -0
  85. package/dist/session/header/session-site-header.js +2 -1
  86. package/dist/session/panel/session-action-panel-column.js +11 -1
  87. package/dist/session/panel/session-audit-shared.d.ts +0 -4
  88. package/dist/session/panel/session-audit-shared.js +1 -1
  89. package/dist/session/panel/session-changes-shared.d.ts +2 -1
  90. package/dist/session/prompts/session-permission-prompt.js +6 -3
  91. package/dist/session/status/provisioning-failure.d.ts +2 -1
  92. package/dist/session/status/session-error-banner.js +8 -6
  93. package/dist/session/status/session-starting-loader.d.ts +24 -31
  94. package/dist/session/status/session-starting-loader.js +32 -81
  95. package/dist/session/thread/chat-minimap-items.d.ts +4 -4
  96. package/dist/session/thread/first-prompt-handover.d.ts +3 -2
  97. package/dist/session/thread/message-parsing.d.ts +3 -3
  98. package/dist/session/thread/session-chat.js +3 -10
  99. package/dist/session/thread/session-history-scroll.d.ts +2 -1
  100. package/dist/session/thread/session-thread.js +1 -1
  101. package/dist/styles.css +1 -1
  102. package/dist/terminal/pty-connection.d.ts +5 -4
  103. package/dist/workspace/index.d.ts +1 -0
  104. package/dist/workspace/index.js +1 -0
  105. package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
  106. package/dist/workspace/saved-session-skeleton.d.ts +5 -1
  107. package/dist/workspace/saved-session-skeleton.js +5 -4
  108. package/dist/workspace/session-workspace.js +4 -5
  109. package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
  110. package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
  111. package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
  112. package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
  113. package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
  114. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  115. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  116. package/dist/workspace/sidebar/project-session-list.js +12 -3
  117. package/dist/workspace/sidebar/session-brief-hover-card.js +5 -8
  118. package/package.json +4 -4
  119. package/skills/kortix-ui/references/modules.md +2 -0
  120. package/dist/session/status/use-readiness-settling.d.ts +0 -18
  121. package/dist/session/status/use-readiness-settling.js +0 -43
  122. package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
  123. 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,7 +164,7 @@ 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();
@@ -184,7 +186,18 @@ function AgentPageFrame({ header, section, sections, sectionHref, pane, footer,
184
186
  // The Settings rail's row dialect (`project-settings-page.tsx`).
185
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));
186
188
  };
187
- 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, {})] }));
188
201
  }
189
202
  function CenteredState({ children }) {
190
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 }) }));
@@ -237,7 +250,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
237
250
  : null;
238
251
  const makeDefault = useDefaultAgent(projectId);
239
252
  const pathname = useUiPathname();
240
- 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
241
254
  ? tI18nComplete.raw('texte8cb80e5c5cb')
242
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: () => {
243
256
  setConfirmEditSource(false);
@@ -248,7 +261,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
248
261
  function AgentChips({ agent, config, size, }) {
249
262
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
250
263
  const { showMode, isDefault, disabled } = agentStatus(agent, config);
251
- 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] }));
252
265
  }
253
266
  /**
254
267
  * Breadcrumb, avatar, name, chips, actions — the full header at the top of
@@ -257,7 +270,7 @@ function AgentChips({ agent, config, size, }) {
257
270
  */
258
271
  function AgentHeader({ projectId, agent, config, canWrite, children, }) {
259
272
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
260
- 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] }));
261
274
  }
262
275
  // ─── Editable body ─────────────────────────────────────────────────────────
263
276
  /** What a v1 project, or a reader without write, can still see. */
@@ -289,6 +302,22 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
289
302
  const sections = useMemo(() => agentConfigSectionKeys(schemaVersion).filter((k) => drivesOn || k !== 'drives'), [schemaVersion, drivesOn]);
290
303
  const registryOptions = useMemo(() => registryGrantOptions({ manifest_raw: config.manifest_raw, agents: toArray(config.agents) }, agent.name), [config.manifest_raw, config.agents, agent.name]);
291
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]);
292
321
  const onSave = useCallback(async () => {
293
322
  if (!editor.isDirty || update.isPending)
294
323
  return;
@@ -306,7 +335,7 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
306
335
  // shared across topics and nothing is lost.
307
336
  const leaveGuard = useUnsavedChangesGuard(editor.isDirty, pathname);
308
337
  useSaveShortcut(onSave);
309
- 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'
310
339
  ? 'Required. This is how other agents decide to call it.'
311
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: () => {
312
341
  setConfirmDiscard(false);
@@ -318,34 +347,40 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
318
347
  }
319
348
  /**
320
349
  * The Overview topic — what the agent IS: one line of description and the
321
- * 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.
322
353
  */
323
354
  function OverviewPane({ description, onDescriptionChange, descriptionHelp, prompt, onPromptChange, }) {
324
355
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
325
- 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 })] }));
326
358
  }
327
359
  /**
328
360
  * The instructions — the agent's system prompt, and the thing that most IS
329
361
  * the agent. Two views of one field: Edit is the textarea, Preview renders
330
362
  * the markdown the way the agent's source file reads. It opens on Preview
331
363
  * when there is something to read and on Edit when there is not; the toggle
332
- * is a segmented pair at the section's right edge, the same control the list
333
- * pages use for their filters.
364
+ * is the segmented `TabsList` at the section heading's right edge.
334
365
  */
335
- function InstructionsPanel({ value, onChange, }) {
366
+ function InstructionsSection({ value, onChange, }) {
336
367
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
337
368
  const [view, setView] = useState(value.trim() ? 'preview' : 'edit');
338
- 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 }) })) }));
339
370
  }
340
371
  /**
341
372
  * The save bar. Exists only while there is something to save — an always-on
342
- * disabled pair of buttons is chrome that never earns its row. It sits across
343
- * the foot of the page, outside both scrollers, so it is reachable from any
344
- * 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.
345
380
  */
346
381
  function SaveBar({ dirty, pending, onDiscard, onSave, }) {
347
382
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
348
- 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 }));
349
384
  }
350
385
  /**
351
386
  * Mod+S saves. The page is a document you edit, and every editor a person
@@ -445,10 +480,10 @@ function ReadOnlyAgentPage({ projectId, agent, config, canWrite, showUpgradeHint
445
480
  });
446
481
  const pathname = useUiPathname();
447
482
  const section = useAgentSection(isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS);
448
- 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: () => {
449
484
  void fileQuery.refetch();
450
- }, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') })] }));
451
- 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', {
452
487
  code: (chunks) => _jsx("span", { className: "font-mono", children: chunks }),
453
- }) })) : 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') })] }))] }) }) }));
454
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.
@@ -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
@@ -14,7 +14,7 @@ import { EntityAvatar } from '../../../primitives/entity-avatar.js';
14
14
  import { Skeleton } from '../../../primitives/skeleton.js';
15
15
  import { EmptyState } from '../../../primitives/empty-state.js';
16
16
  import { ErrorState } from '../../../primitives/error-state.js';
17
- import { appNameKey, catalogEntryConnectors, } from '../catalog/catalog-entry.js';
17
+ import { appNameKey, catalogEntryConnectors, discoverEntryIcon, } from '../catalog/catalog-entry.js';
18
18
  import { listConnectCatalogPage, useConnectProviderStatus, } from '../catalog/use-catalog.js';
19
19
  import { connectorDisplayName, connectorSummary, } from '../connector-filter.js';
20
20
  import { appRefFromLocation, connectorHref, connectorsHref, } from '../connector-routes.js';
@@ -37,6 +37,22 @@ const SURFACE_ROW = 'flex items-center gap-3 px-4 py-3';
37
37
  /** One bordered group of rows, divided. Shared by both lists on the page. */
38
38
  const ROW_GROUP = 'bg-popover divide-y overflow-hidden rounded-md border';
39
39
  const surfaceLabel = (kind) => (kind === 'openapi' ? 'OpenAPI' : kind.toUpperCase());
40
+ /**
41
+ * The app page's icon tile (`ConnectorAppIcon`, size `lg`): the resolved
42
+ * favicon, or the glyph tile when the record has none — and the same glyph
43
+ * tile when the URL fails to load, which used to render the browser's
44
+ * broken-image glyph beside the title. Mirrors the catalogue card's
45
+ * `ConnectorIcon`. The latch is per URL (`failedUrl`), so the late-arriving
46
+ * managed logo — which swaps the URL — gets a fresh try without a call-site
47
+ * key.
48
+ */
49
+ function AppIcon({ icon, name }) {
50
+ const [failedUrl, setFailedUrl] = useState(null);
51
+ if (!icon || failedUrl === icon) {
52
+ return _jsx(EntityAvatar, { icon: GlobeIcon, size: "lg", label: name });
53
+ }
54
+ 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, referrerPolicy: "no-referrer", className: "size-9 object-contain", onError: () => setFailedUrl(icon) }) }));
55
+ }
40
56
  /**
41
57
  * `/projects/<id>/customize/connectors/<app>` — one catalogue app: what it is,
42
58
  * the one Install button, the connectors this project already has for it, and
@@ -148,9 +164,7 @@ export function AppPage({ projectId, appSegment }) {
148
164
  infoCells.push({ key: 'runs', label: t('infoRunsOver'), value: primary.kind });
149
165
  if (waysCount > 1)
150
166
  infoCells.push({ key: 'ways', label: t('waysToConnect'), value: String(waysCount) });
151
- return (_jsxs(DetailPageShell, { children: [_jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: connectorsHref(projectId), children: [_jsx(CaretLeftIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('textc3d2e79ebdd0')] }) }), _jsxs("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [resolved.icon ? (
152
- // The connector page's icon tile (`ConnectorAppIcon`, size `lg`).
153
- _jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: resolved.icon, alt: "", width: 36, height: 36, referrerPolicy: "no-referrer", className: "size-9 object-contain" }) })) : (_jsx(EntityAvatar, { icon: GlobeIcon, size: "lg", label: resolved.name })), _jsx("h1", { className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: resolved.name })] }), primary ? (_jsx("div", { className: "shrink-0", children: installMenu('header', primary.target, 'default', resolved.name) })) : null] }), infoCells.length > 0 || resolved.description ? (_jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [infoCells.length > 0 ? (_jsx("dl", { className: "flex flex-wrap gap-x-8 gap-y-3 px-4 py-3", children: infoCells.map((cell) => (_jsxs("div", { className: "min-w-0 space-y-0.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground truncate text-sm font-medium", children: cell.value })] }, cell.key))) })) : null, resolved.description ? (_jsxs("div", { className: "space-y-1.5 px-4 py-4", children: [_jsx("h2", { className: "text-muted-foreground text-xs", children: t('overview') }), _jsx("p", { className: "text-foreground text-sm text-pretty", children: resolved.description })] })) : null] })) : null, inProject.length > 0 ? (_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-foreground text-sm font-medium", children: projectName ? t('installedIn', { project: projectName }) : t('installedHere') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('installedHint') })] }), _jsx("ul", { className: ROW_GROUP, children: inProject.map((connector) => {
167
+ return (_jsxs(DetailPageShell, { children: [_jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: connectorsHref(projectId), children: [_jsx(CaretLeftIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('textc3d2e79ebdd0')] }) }), _jsxs("header", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx(AppIcon, { icon: resolved.icon, name: resolved.name }), _jsx("h1", { className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: resolved.name })] }), primary ? (_jsx("div", { className: "shrink-0", children: installMenu('header', primary.target, 'default', resolved.name) })) : null] }), infoCells.length > 0 || resolved.description ? (_jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [infoCells.length > 0 ? (_jsx("dl", { className: "flex flex-wrap gap-x-8 gap-y-3 px-4 py-3", children: infoCells.map((cell) => (_jsxs("div", { className: "min-w-0 space-y-0.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground truncate text-sm font-medium", children: cell.value })] }, cell.key))) })) : null, resolved.description ? (_jsxs("div", { className: "space-y-1.5 px-4 py-4", children: [_jsx("h2", { className: "text-muted-foreground text-xs", children: t('overview') }), _jsx("p", { className: "text-foreground text-sm text-pretty", children: resolved.description })] })) : null] })) : null, inProject.length > 0 ? (_jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx("h2", { className: "text-foreground text-sm font-medium", children: projectName ? t('installedIn', { project: projectName }) : t('installedHere') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('installedHint') })] }), _jsx("ul", { className: ROW_GROUP, children: inProject.map((connector) => {
154
168
  const tone = connectorStatusTone(connector);
155
169
  return (_jsx("li", { children: _jsxs(Link, { href: connectorHref(projectId, connector.slug, { app }), className: "hover:bg-muted/50 flex items-center gap-3 px-4 py-3 transition-colors", children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: connectorDisplayName(connector) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: connectorSummary(connector, providerLabel(connector.provider)) })] }), _jsx("span", { className: "flex w-28 shrink-0 items-center", children: _jsx(ConnectorStatusBadge, { tone: tone }) }), _jsxs("span", { className: "text-muted-foreground flex w-16 shrink-0 items-center justify-end gap-1 text-xs", children: [t('open'), _jsx(CaretRightIcon, { "aria-hidden": true, className: "size-4" })] })] }) }, connector.slug));
156
170
  }) })] })) : null, resolved.surfaces.length === 0 && resolved.references.length === 0 ? (_jsx(EmptyState, { icon: GlobeIcon, size: "sm", title: tI18nComplete.raw('text363579e1d66b'), description: tI18nComplete.raw('text4d286e47d580') })) : null, resolved.surfaces.length > 1 || resolved.references.length > 0 ? (_jsxs("section", { className: "space-y-3", children: [_jsx("h2", { className: "text-foreground text-sm font-medium", children: t('waysToConnect') }), _jsxs("ul", { className: ROW_GROUP, children: [resolved.surfaces.map((surface) => (_jsxs("li", { className: SURFACE_ROW, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: surface.label }), surface.kind ? (_jsx(Badge, { variant: "muted", size: "xs", children: surface.kind })) : null] }), installMenu(surface.key, surface.target, 'secondary', surface.label)] }, surface.key))), resolved.references.map((reference) => (_jsxs("li", { className: SURFACE_ROW, children: [_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "text-foreground truncate text-sm font-medium", children: reference.label }), _jsx(Badge, { variant: "muted", size: "xs", children: reference.kind })] }), reference.href ? (_jsx(Button, { asChild: true, variant: "outline", size: "sm", className: "shrink-0", children: _jsxs("a", { href: reference.href, target: "_blank", rel: "noreferrer", children: [tI18nComplete.raw('text7af023c43013'), _jsx(ArrowSquareOutIcon, { className: "size-3.5 shrink-0" })] }) })) : (_jsx(Badge, { variant: "secondary", size: "sm", children: tI18nComplete.raw('textdf0453d185c4') }))] }, reference.key)))] })] })) : null, mcpApp ? (_jsx(AddMcpServerDialog, { projectId: projectId, app: mcpApp, open: true, onOpenChange: (open) => !open && setMcpApp(null), takenSlugs: connectors.map((connector) => connector.slug), onAdded: () => void connectorsQuery.refetch() }, mcpApp.url)) : null] }));
@@ -231,7 +245,7 @@ function useAppData(projectId, app) {
231
245
  name: managed?.name ?? detail.item.name,
232
246
  description: detail.item.description ?? managed?.description ?? null,
233
247
  // The managed logo is sharp where the catalogue's favicon is not.
234
- icon: managed?.imgSrc ?? detail.item.icon,
248
+ icon: managed?.imgSrc ?? discoverEntryIcon(detail.item),
235
249
  surfaces: [
236
250
  ...installableVariants(detail.variants).map((variant, index) => ({
237
251
  key: `${variant.kind}:${variant.id}`,
@@ -1,4 +1,4 @@
1
- import { type AdminConnector } from '@kortix/sdk';
1
+ import type { AdminConnector } from '@kortix/sdk';
2
2
  interface ConnectorModalProps {
3
3
  projectId: string;
4
4
  /** The connector to show, or `null` when it has not resolved yet. `open` is
@@ -6,10 +6,8 @@ interface ConnectorModalProps {
6
6
  connector: AdminConnector | null;
7
7
  canWrite: boolean;
8
8
  open: boolean;
9
- /** Open on a selection whose record is still loading — `?c=<slug>` on a cold
10
- * page, which is how every OAuth 2.0 return arrives. Renders the shell so
11
- * the modal is present from the first frame instead of appearing on its own
12
- * once the list lands. */
9
+ /** Open on a selection whose record is still loading. Renders the shell so
10
+ * the modal is present from the first frame. */
13
11
  isResolving?: boolean;
14
12
  onOpenChange: (open: boolean) => void;
15
13
  /** Refetch every authorization-derived query. Every mutation below calls it. */
@@ -18,15 +16,14 @@ interface ConnectorModalProps {
18
16
  onRemoved: () => void;
19
17
  }
20
18
  /**
21
- * Connector detail — header identity + tab nav + content pane.
19
+ * One connector over the agent editor, without leaving it (a navigation would
20
+ * drop the agent's unsaved draft). The body IS the connector page
21
+ * (`ConnectorDetailView`): the same header, status strip, notice, tabs and
22
+ * split credential sheet, so the modal and the page cannot drift. The modal
23
+ * adds only a close button and keeps its tab in state instead of the URL.
22
24
  *
23
- * Header: icon, name, description, primary connect action.
24
- * Left: Accounts / Tools / Triggers / Settings nav.
25
- * Right: the active tab.
26
- *
27
- * `ConnectorModalBody` is keyed on `connector.slug` so picking a different card
28
- * while the modal stays open resets the active tab without remounting
29
- * `Modal`/`ModalContent` (which would replay the open animation).
25
+ * The view is keyed on `connector.slug` so picking a different connector while
26
+ * the modal stays open resets the tab without replaying the open animation.
30
27
  */
31
28
  export declare function ConnectorModal({ projectId, connector, canWrite, open, isResolving, onOpenChange, onChanged, onRemoved, }: ConnectorModalProps): import("react").JSX.Element;
32
29
  export {};