@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
@@ -8,7 +8,8 @@ import { contract, FRESHNESS, qk, useFeatureFlag, useProjectAccountId } from '@k
8
8
  import { MagnifyingGlassIcon, PlugIcon, PlugsConnectedIcon } from '@phosphor-icons/react';
9
9
  import { useQuery, useQueryClient } from '@tanstack/react-query';
10
10
  import { lazyClient } from '../../lib/lazy-client.js';
11
- import { useUiPathname, useUiRouter, useUiSearchParams } from '../../providers/ui-provider.js';
11
+ import { useUiPathname, useUiRouter, useUiSearchParams, useUiShallowNavigate, } from '../../providers/ui-provider.js';
12
+ import { useDebounce } from '../../hooks/use-debounce.js';
12
13
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
13
14
  import { PoliciesPanel } from '../../workspace/projects/policies-panel.js';
14
15
  import { Button } from '../../primitives/button.js';
@@ -37,7 +38,7 @@ import { useTunnelConnections } from '@kortix/sdk/react';
37
38
  import { useSlashFocus } from '../../internal/hooks/index.js';
38
39
  import { connectorDisplayName, connectorSummary, filterConnectors, } from './connector-filter.js';
39
40
  import { ConnectorMovesBanner } from './connector-moves-banner.js';
40
- import { appHref, appRefFromEntry, connectorHref, legacyDetailRedirect } from './connector-routes.js';
41
+ import { appHref, appRefFromEntry, connectorHref, connectorSearchFromParams, legacyDetailRedirect, withConnectorSearch, } from './connector-routes.js';
41
42
  import { useInstall } from './install/use-install.js';
42
43
  /**
43
44
  * The click-gated Add form, split out of this route's initial chunk.
@@ -172,7 +173,11 @@ export function ConnectorsPage({ projectId }) {
172
173
  const canWrite = useProjectCan(projectId, PROJECT_ACTIONS.PROJECT_CONNECTOR_WRITE, { accountId }).allowed ===
173
174
  true;
174
175
  const queryClient = useQueryClient();
175
- const [query, setQuery] = useState('');
176
+ const { search, router, pathname, replaceParams, setScope, rulesOpen, setRulesOpen } = useConnectorNavigation(projectId);
177
+ // The search lives in `?q=`: opening a connector unmounts this page, and
178
+ // browser Back must bring the same results back (KRTX-3018).
179
+ const [query, setQuery] = useState(() => connectorSearchFromParams(search));
180
+ useSearchInUrl(query, search, pathname);
176
181
  const [panel, setPanel] = useState(null);
177
182
  // The catalogue app whose own MCP server the MCP dialog connects. Null for
178
183
  // a server the user pastes (New → MCP server).
@@ -180,7 +185,6 @@ export function ConnectorsPage({ projectId }) {
180
185
  const [computerOpen, setComputerOpen] = useState(false);
181
186
  const tSharing = useI18nTranslations('accessSharing');
182
187
  const tPages = useI18nTranslations('connectorPages');
183
- const { search, router, pathname, replaceParams, setScope, rulesOpen, setRulesOpen } = useConnectorNavigation(projectId);
184
188
  const connectorsQuery = useQuery({
185
189
  queryKey: qk.project.connectors(projectId),
186
190
  queryFn: () => listConnectors(projectId, { includeSchemas: false }),
@@ -374,6 +378,21 @@ export function ConnectorsPage({ projectId }) {
374
378
  router.push(connectorHref(projectId, slug, { addAccount }));
375
379
  } }) })] }) })] }) }), _jsxs(SplitSheetContent, { children: [_jsxs(SplitSheetHeader, { children: [_jsx(SplitSheetTitle, { children: tI18nComplete.raw('text1d59a5e09714') }), _jsx(SplitSheetDescription, { children: tI18nComplete.raw('text014d10bd3c64') })] }), _jsx(SplitSheetBody, { children: _jsx(PoliciesPanel, { projectId: projectId }) })] })] }));
376
380
  }
381
+ /**
382
+ * Mirrors the settled search into `?q=` with a shallow replace: no history
383
+ * entry per keystroke, and no server round trip for a view already on screen.
384
+ */
385
+ function useSearchInUrl(query, search, pathname) {
386
+ const shallowNavigate = useUiShallowNavigate();
387
+ const { debouncedValue } = useDebounce(query.trim(), 300);
388
+ useEffect(() => {
389
+ if (connectorSearchFromParams(search) === debouncedValue)
390
+ return;
391
+ const params = withConnectorSearch(new URLSearchParams(search?.toString() ?? ''), debouncedValue);
392
+ const suffix = params.toString();
393
+ shallowNavigate(suffix ? `${pathname}?${suffix}` : pathname, { replace: true });
394
+ }, [debouncedValue, search, pathname, shallowNavigate]);
395
+ }
377
396
  function useConnectorNavigation(projectId) {
378
397
  const search = useUiSearchParams();
379
398
  const router = useUiRouter();
@@ -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}`,
@@ -49,13 +49,11 @@ export function ConnectorAdvancedTools({ connector, advancedRules, toolRules, lo
49
49
  // re-fired whenever a rule was added or removed, so the panel sprang back open
50
50
  // moments after the user collapsed it, which read as the disclosure opening by
51
51
  // itself. After the first reveal the panel is the user's to control.
52
- const revealedExistingRules = useRef(false);
53
- useEffect(() => {
54
- if (revealedExistingRules.current || advancedRules.length === 0)
55
- return;
56
- revealedExistingRules.current = true;
52
+ const [revealedExistingRules, setRevealedExistingRules] = useState(false);
53
+ if (!revealedExistingRules && advancedRules.length > 0) {
54
+ setRevealedExistingRules(true);
57
55
  setAdvancedOpen(true);
58
- }, [advancedRules.length]);
56
+ }
59
57
  const savePatternRules = () => onSave([...toolRules, ...draftToRules(draft)]);
60
58
  return (_jsx(_Fragment, { children: hasTools || advancedRules.length > 0 ? (_jsxs(Disclosure, { variant: "outline", className: "overflow-hidden", open: advancedOpen, onOpenChange: setAdvancedOpen, children: [_jsx(DisclosureTrigger, { variant: "outline", children: _jsxs(Button, { variant: "popover", children: [tI18nComplete.raw('text9f088dbebd6c'), advancedRules.length > 0 ? (_jsx(Badge, { variant: "secondary", size: "sm", children: advancedRules.length })) : null, _jsx("span", { className: "text-muted-foreground ml-auto text-xs font-normal", children: tI18nComplete.raw('text59da2efb2423') })] }) }), _jsx(DisclosureContent, { variant: "outline", contentClassName: "border-border border-t", children: _jsxs("div", { className: "space-y-5 px-4 py-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text594bdd4c19b2') }), _jsx("p", { className: "text-muted-foreground mt-0.5 text-xs text-pretty", children: tI18nComplete.raw('textf900377c2048') })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [sensitivePending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, _jsx(Switch, { checked: connector.sensitive === true, onCheckedChange: onSensitive, disabled: frozen || sensitivePending, "aria-label": tI18nComplete.raw('text594bdd4c19b2') })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: tI18nComplete.raw('text380c0b05bcfc') }), _jsxs("p", { className: "text-muted-foreground text-xs text-pretty", children: [tI18nComplete.raw('textde00adeaf21b'), _jsx("code", { className: "font-mono", children: "delete_*" }), tI18nComplete.raw('text3a13855164e7'), _jsx("code", { className: "font-mono", children: "/^send/i" }), tI18nComplete.raw('texte45481e75a5e')] }), draft.map((row) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: row.match, placeholder: tI18nComplete.raw('text74ad536dca05'), variant: "popover", size: "xs", className: "flex-1 font-mono", "aria-label": tI18nComplete.raw('text5c9c672f4cd3'), disabled: frozen, onChange: (event) => setDraft((rows) => rows.map((candidate) => candidate.id === row.id
61
59
  ? { ...candidate, match: event.target.value }
@@ -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 {};
@@ -1,117 +1,34 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations } from '../../../i18n/index.js';
4
- import { listPipedreamApps } from '@kortix/sdk';
5
- import { KeyIcon, PlusIcon } from '@phosphor-icons/react';
6
4
  import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
7
- import { useQuery } from '@tanstack/react-query';
8
- import { useState } from 'react';
9
- import { Badge } from '../../../primitives/badge.js';
10
5
  import { Button } from '../../../primitives/button.js';
11
- import { InfoBanner } from '../../../primitives/info-banner.js';
12
- import Loading from '../../../primitives/loading.js';
13
- import { Modal, ModalBody, ModalClose, ModalContent, ModalHeader, ModalTitle, } from '../../../primitives/modal.js';
6
+ import { Modal, ModalClose, ModalContent, ModalTitle } from '../../../primitives/modal.js';
14
7
  import { Skeleton } from '../../../primitives/skeleton.js';
15
- import { Tabs, TabsList, TabsTrigger } from '../../../primitives/tabs.js';
16
- import { ComputerConnectModal } from '../../../internal/tunnel/computer-connect.js';
17
- import { SetCredentialModal } from '../../sections/connectors-view.js';
18
- import { ConnectorAppIcon, ConnectorStatusBadge, } from '../connector-identity.js';
19
- import { cn } from '../../../internal/lib/utils.js';
20
- import { ButtonGroup } from '../../../primitives/button-group.js';
21
8
  import { Close } from '../../../icons/close.js';
22
- import { foldKey } from '../catalog/catalog-entry.js';
23
- import { ConnectorDetailTabs } from './connector-detail-tabs.js';
24
- import { CONNECTOR_TAB_LABEL_KEY, connectorTabs } from './connector-tabs.js';
25
- import { useConnectorEventTriggers } from './connector-triggers.js';
26
- import { useConnectorDetail } from './use-connector-detail.js';
9
+ import { ConnectorDetailView } from './connector-page.js';
10
+ /** The scroll column inside the modal: the page's column, tighter. */
11
+ const MODAL_COLUMN = 'w-full space-y-6 px-6 py-6';
27
12
  /**
28
- * Connector detail — header identity + tab nav + content pane.
13
+ * One connector over the agent editor, without leaving it (a navigation would
14
+ * drop the agent's unsaved draft). The body IS the connector page
15
+ * (`ConnectorDetailView`): the same header, status strip, notice, tabs and
16
+ * split credential sheet, so the modal and the page cannot drift. The modal
17
+ * adds only a close button and keeps its tab in state instead of the URL.
29
18
  *
30
- * Header: icon, name, description, primary connect action.
31
- * Left: Accounts / Tools / Triggers / Settings nav.
32
- * Right: the active tab.
33
- *
34
- * `ConnectorModalBody` is keyed on `connector.slug` so picking a different card
35
- * while the modal stays open resets the active tab without remounting
36
- * `Modal`/`ModalContent` (which would replay the open animation).
19
+ * The view is keyed on `connector.slug` so picking a different connector while
20
+ * the modal stays open resets the tab without replaying the open animation.
37
21
  */
38
22
  export function ConnectorModal({ projectId, connector, canWrite, open, isResolving = false, onOpenChange, onChanged, onRemoved, }) {
39
- return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "bg-popover h-[80vh] space-y-0 lg:max-w-6xl", "aria-describedby": undefined, showCloseButton: false, children: connector ? (_jsx(ConnectorModalBody, { projectId: projectId, connector: connector, canWrite: canWrite, onChanged: onChanged, onRemoved: onRemoved }, connector.slug)) : isResolving ? (_jsx(ConnectorModalSkeleton, {})) : null }) }));
23
+ const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
24
+ return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: _jsx(ModalContent, { className: "bg-background flex max-h-[92vh] min-h-[85vh] flex-col space-y-0 overflow-hidden p-0 lg:max-w-5xl", "aria-describedby": undefined, showCloseButton: false, children: connector ? (_jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: _jsx(ConnectorDetailView, { projectId: projectId, connector: connector, canWrite: canWrite, onChanged: onChanged, onRemoved: onChanged, onLeave: onRemoved, urlState: false, columnClassName: MODAL_COLUMN, wrapTitle: (title) => _jsx(ModalTitle, { asChild: true, children: title }), headerEnd: _jsx(ModalClose, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-sm", className: "shrink-0", "aria-label": tI18nComplete.raw('text7d9eb7acb13e'), children: _jsx(Close, { className: "text-foreground size-4 stroke-1" }) }) }) }, connector.slug) })) : isResolving ? (_jsx(ConnectorModalSkeleton, {})) : null }) }));
40
25
  }
41
26
  /**
42
- * The shell, while `?c=<slug>` is resolving against a list that has not
43
- * arrived. Shape-matched to `ConnectorModalBody` — same header height, same
44
- * left rail width — so the handover fills the placeholders in place instead of
45
- * relaying the modal out from under the pointer.
46
- *
47
- * The `ModalTitle` is real and visually hidden: Radix's Dialog needs an
48
- * accessible name at all times, and a screen reader announcing "Loading
49
- * connector" is the honest answer during this window.
27
+ * The shell while the selection resolves. Shape-matched to the view — icon and
28
+ * name, the strip, the tabs, two rows — so the handover fills it in place.
29
+ * The visually hidden title gives the dialog its accessible name meanwhile.
50
30
  */
51
31
  function ConnectorModalSkeleton() {
52
32
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
53
- return (_jsxs(_Fragment, { children: [_jsxs(ModalHeader, { className: "flex-row items-start gap-2.5 border-b pb-4", children: [_jsx(VisuallyHidden, { children: _jsx(ModalTitle, { children: tI18nComplete.raw('text0c21b0363778') }) }), _jsx("span", { className: "p-1", children: _jsx(Skeleton, { className: "size-10 rounded-md" }) }), _jsxs("div", { className: "min-w-0 flex-1 space-y-2 pt-1", children: [_jsx(Skeleton, { className: "h-4 w-40 rounded-sm" }), _jsx(Skeleton, { className: "h-3 w-64 rounded-sm" })] })] }), _jsx(ModalBody, { className: "max-h-[70vh] overflow-hidden p-0", children: _jsxs("div", { className: "flex min-h-0 flex-col lg:h-[70vh] lg:flex-row", children: [_jsx("div", { className: "lg:border-border shrink-0 gap-1 p-3 lg:h-full lg:w-64 lg:border-r", children: _jsx("div", { className: "hidden space-y-1.5 lg:block", children: [0, 1, 2].map((i) => (_jsx(Skeleton, { className: "h-9 w-full rounded-md" }, i))) }) }), _jsxs("div", { className: "min-w-0 flex-1 space-y-3 px-5 py-4 lg:px-6", children: [_jsx(Skeleton, { className: "h-5 w-32 rounded-sm" }), _jsx(Skeleton, { className: "h-20 w-full rounded-md" }), _jsx(Skeleton, { className: "h-20 w-full rounded-md" })] })] }) })] }));
54
- }
55
- function ConnectorModalBody({ projectId, connector, canWrite, onChanged, onRemoved, }) {
56
- const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
57
- const { displayName, isManagedProvider, isChannel, isComputer, connectionsQuery, connected, canManageConnections, headerCta, connectPending, refreshAccounts, replaceSoleAccount, startPrivateSession, credentialTarget, setCredentialTarget, computerOpen, setComputerOpen, } = useConnectorDetail({ projectId, connector, canWrite, onChanged });
58
- // Opens the account list's "Add account" dialog: name and who may use it.
59
- const [addRequest, setAddRequest] = useState(0);
60
- const tSharing = useTranslations('accessSharing');
61
- const isPipedream = connector.provider === 'pipedream';
62
- const eventTriggers = useConnectorEventTriggers(projectId, connector);
63
- const tabs = connectorTabs(connector, { canWrite, hasEvents: eventTriggers.hasEvents });
64
- const [selectedTab, setSelectedTab] = useState('accounts');
65
- const tab = tabs.includes(selectedTab) ? selectedTab : (tabs[0] ?? 'accounts');
66
- // Best-effort catalogue description. Never blocks first paint — the header
67
- // renders without it, then fills in. That is the main open-latency fix:
68
- // previously this query sat in the critical path of feeling "ready".
69
- const appDescriptionQuery = useQuery({
70
- queryKey: ['connector-app-description', projectId, connector.slug, displayName],
71
- queryFn: async () => {
72
- const result = await listPipedreamApps(projectId, displayName);
73
- const match = result.apps.find((app) => foldKey(app.slug) === foldKey(connector.slug) ||
74
- foldKey(app.name) === foldKey(displayName));
75
- return match?.description ?? null;
76
- },
77
- enabled: isPipedream,
78
- staleTime: 5 * 60_000,
79
- });
80
- const appDescription = isPipedream ? (appDescriptionQuery.data ?? null) : null;
81
- const addAccountMenu = isComputer ? (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: () => setComputerOpen(true), children: [_jsx(PlusIcon, { className: "size-4 shrink-0", weight: "bold" }), tI18nComplete.raw('text1a2303ede074')] })) : (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: () => {
82
- setSelectedTab('accounts');
83
- setAddRequest((n) => n + 1);
84
- }, disabled: connectPending, children: [_jsx(PlusIcon, { className: "size-4 shrink-0", weight: "bold" }), tSharing('addAccount')] }));
85
- return (_jsxs(_Fragment, { children: [_jsxs(ModalHeader, { className: "flex-row items-start gap-2.5 border-b pb-4", children: [_jsx("span", { className: "p-1", children: _jsx(ConnectorAppIcon, { connector: connector, size: "lg" }) }), _jsxs("div", { className: "min-w-0 flex-1 space-y-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1", children: [_jsx(ModalTitle, { className: "truncate text-lg font-semibold", children: displayName }), _jsx(ConnectorStatusBadge, { connector: connector })] }), appDescription ? (_jsx("p", { className: "text-muted-foreground text-sm text-pretty", children: appDescription })) : null] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(ButtonGroup, { className: "shrink-0", children: [headerCta === 'connect' ? addAccountMenu : null, headerCta === 'finish' ? (_jsxs(Button, { size: "sm", variant: "outline", className: "gap-1.5 active:scale-[0.96]", onClick: () => setSelectedTab('accounts'), children: [_jsx(KeyIcon, { className: "size-4 shrink-0" }), isManagedProvider
86
- ? tI18nComplete.raw('text1a2303ede074')
87
- : tI18nComplete.raw('text3d6627454174')] })) : null, headerCta === 'replace' ? (_jsxs(Button, { size: "sm", variant: "outline", className: "gap-1.5 active:scale-[0.96]", onClick: replaceSoleAccount, disabled: connectPending, children: [isManagedProvider ? (connectPending ? (_jsx(Loading, { className: "size-4 shrink-0" })) : null) : (_jsx(KeyIcon, { className: "size-4 shrink-0" })), isManagedProvider
88
- ? tI18nComplete.raw('textbf8a9eab9e7e')
89
- : tI18nComplete.raw('text54483ce856e0')] })) : null] }), _jsx(ModalClose, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "size-8 shrink-0 rounded-md", "aria-label": tI18nComplete.raw('text7d9eb7acb13e'), children: _jsx(Close, { className: "text-foreground size-4 stroke-1" }) }) })] })] }), _jsxs(ModalBody, { className: "flex max-h-[70vh] flex-col overflow-hidden p-0", children: [!connected && !isChannel && !isComputer && connectionsQuery.isSuccess ? (_jsx(InfoBanner, { tone: "warning", title: tI18nComplete('text6506d9ea4341', { value0: displayName }), className: "shrink-0 rounded-none border-x-0 border-t-0", action: headerCta === 'connect' ? (addAccountMenu) : headerCta === 'finish' ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setSelectedTab('accounts'), children: isManagedProvider
90
- ? tI18nComplete.raw('text1a2303ede074')
91
- : tI18nComplete.raw('text3d6627454174') })) : undefined, children: canWrite
92
- ? tI18nComplete.raw('text929505ef815a')
93
- : tI18nComplete.raw('text6a05ddf8cca1') })) : null, _jsxs(Tabs, { value: tab, onValueChange: (next) => setSelectedTab(next), className: "flex min-h-0 flex-1 flex-col gap-0 overflow-y-auto lg:flex-row lg:overflow-hidden", children: [_jsx(TabsList, { type: "underline", underlineSize: "md", size: "sm", "aria-label": `${displayName} sections`, className: cn('h-auto w-full shrink-0 justify-start gap-1 rounded-none px-2', 'overflow-x-auto', 'lg:border-border lg:h-full lg:w-64 lg:flex-col lg:items-stretch lg:gap-0.5', 'lg:overflow-x-visible lg:overflow-y-auto lg:border-r lg:border-b-0 lg:p-3', 'lg:**:data-[slot=tabs-trigger]:after:hidden', 'lg:**:data-[slot=tabs-trigger]:h-auto lg:**:data-[slot=tabs-trigger]:w-full', 'lg:**:data-[slot=tabs-trigger]:justify-between lg:**:data-[slot=tabs-trigger]:rounded-md', 'lg:**:data-[slot=tabs-trigger]:px-3 lg:**:data-[slot=tabs-trigger]:py-2', 'lg:**:data-[slot=tabs-trigger]:data-[state=active]:bg-primary/6', 'lg:**:data-[slot=tabs-trigger]:data-[state=active]:font-medium', 'lg:**:data-[slot=tabs-trigger]:data-[state=inactive]:hover:bg-primary/3'), children: tabs.map((value) => (_jsxs(TabsTrigger, { value: value, className: "w-fit flex-none gap-2 px-3 py-2.5 active:scale-[0.98] lg:w-full", children: [tI18nComplete.raw(CONNECTOR_TAB_LABEL_KEY[value]), value === 'triggers' ? (_jsx(Badge, { variant: "secondary", size: "sm", children: eventTriggers.rows.length })) : null] }, value))) }), _jsx("div", { className: "bg-popover min-w-0 flex-1 overflow-hidden overflow-y-auto px-5 py-4 lg:px-6", children: _jsx(ConnectorDetailTabs, { connectionsQuery: connectionsQuery, accounts: {
94
- projectId,
95
- connector,
96
- displayName,
97
- canWrite,
98
- canManageConnections,
99
- onChanged,
100
- onStartSession: startPrivateSession,
101
- addRequest,
102
- onAddRequestHandled: () => setAddRequest(0),
103
- onRemoved,
104
- }, settings: {
105
- projectId: projectId,
106
- connector: connector,
107
- displayName: displayName,
108
- onChanged: onChanged,
109
- onRemoved: onRemoved,
110
- } }) })] })] }), _jsx(SetCredentialModal, { projectId: projectId, connector: credentialTarget ? connector : null, connectionId: credentialTarget?.connectionId ?? null, owner: credentialTarget?.owner ?? 'me', open: credentialTarget !== null, onOpenChange: (open) => {
111
- if (!open)
112
- setCredentialTarget(null);
113
- }, onSaved: () => {
114
- setCredentialTarget(null);
115
- refreshAccounts();
116
- } }), isComputer ? (_jsx(ComputerConnectModal, { projectId: projectId, open: computerOpen, onOpenChange: setComputerOpen, onConnected: refreshAccounts })) : null] }));
33
+ return (_jsxs("div", { className: MODAL_COLUMN, "aria-busy": true, children: [_jsx(VisuallyHidden, { children: _jsx(ModalTitle, { children: tI18nComplete.raw('text0c21b0363778') }) }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "size-9 rounded-md" }), _jsx(Skeleton, { className: "h-6 w-48 rounded-sm" })] }), _jsx(Skeleton, { className: "h-16 w-full rounded-md" }), _jsx(Skeleton, { className: "h-9 w-64 rounded-md" }), _jsx(Skeleton, { className: "h-20 w-full rounded-md" })] }));
117
34
  }
@@ -72,8 +72,12 @@ export function noticeButton(notice, target, managed) {
72
72
  }
73
73
  /** A failure line split for the log row: `MCP tools/list failed: HTTP 401` → `401` + `MCP tools/list failed`. */
74
74
  export function splitNoticeReason(reason) {
75
- const match = /[:\s]*\bHTTP (\d{3})\b/.exec(reason);
75
+ const match = /\bHTTP (\d{3})\b/.exec(reason);
76
76
  if (!match)
77
77
  return { code: null, detail: reason };
78
- return { code: match[1], detail: reason.replace(match[0], '').trim() };
78
+ // Drop the `: ` before the status with a loop: a `[:\s]*` regex prefix is quadratic on long whitespace runs.
79
+ let end = match.index;
80
+ while (end > 0 && /[:\s]/.test(reason[end - 1]))
81
+ end--;
82
+ return { code: match[1], detail: (reason.slice(0, end) + reason.slice(match.index + match[0].length)).trim() };
79
83
  }
@@ -1,3 +1,5 @@
1
+ import { type AdminConnector } from '@kortix/sdk';
2
+ import { type ReactNode } from 'react';
1
3
  /** The column `CapabilityPageShell` gives the list page beside this one. */
2
4
  /**
3
5
  * `/projects/<id>/customize/connectors/<app>/<connector>` — one connector, as
@@ -12,6 +14,34 @@ export declare function ConnectorPage({ projectId, appSegment, connectorSlug, }:
12
14
  appSegment: string;
13
15
  connectorSlug: string;
14
16
  }): import("react").JSX.Element;
17
+ export interface ConnectorDetailViewProps {
18
+ projectId: string;
19
+ connector: AdminConnector;
20
+ canWrite: boolean;
21
+ onChanged: () => void;
22
+ /** The connector was deleted: refresh everything but its own queries. */
23
+ onRemoved: () => void;
24
+ /** Above the header: the page's Back link. */
25
+ back?: ReactNode;
26
+ /** At the header's right edge, after its actions: the modal's close button. */
27
+ headerEnd?: ReactNode;
28
+ /** Wraps the `h1`: the modal passes `ModalTitle asChild` for its accessible name. */
29
+ wrapTitle?: (title: ReactNode) => ReactNode;
30
+ /** The view's own connector is gone: leave it (the page routes back, the
31
+ * modal closes). */
32
+ onLeave: () => void;
33
+ /** The tab and the install hand-offs live in the URL on the page. The modal
34
+ * keeps its tab in state, so it never rewrites its host page's URL. */
35
+ urlState?: boolean;
36
+ /** The scroll column's classes; the modal uses tighter padding. */
37
+ columnClassName?: string;
38
+ }
39
+ /**
40
+ * One connector — the strip, its notice, and the Accounts / Tools / Settings
41
+ * tabs. The page (`/connectors/<app>/<connector>`) and the agent editor's
42
+ * connector modal render this same view, so the two cannot drift.
43
+ */
44
+ export declare function ConnectorDetailView({ projectId, connector, canWrite, onChanged, onRemoved, back, headerEnd, wrapTitle, onLeave, urlState, columnClassName, }: ConnectorDetailViewProps): import("react").JSX.Element;
15
45
  /**
16
46
  * The strip's bottom row when something is wrong: a one-line title, the raw
17
47
  * failure as a quiet log line, and the buttons that fix it. No icon tile; the
@@ -99,7 +99,17 @@ export function ConnectorPage({ projectId, appSegment, connectorSlug, }) {
99
99
  }
100
100
  return (_jsx(ConnectorPageBody, { projectId: projectId, connector: connector, canWrite: canWrite, backHref: backHref, onChanged: invalidate, onRemoved: invalidateAfterRemove }, connector.slug));
101
101
  }
102
- function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged, onRemoved, }) {
102
+ function ConnectorPageBody({ backHref, ...props }) {
103
+ const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
104
+ const router = useUiRouter();
105
+ return (_jsx(ConnectorDetailView, { ...props, back: _jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: backHref, children: [_jsx(CaretLeftIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('textc3d2e79ebdd0')] }) }), onLeave: () => router.push(connectorsHref(props.projectId, 'connected')) }));
106
+ }
107
+ /**
108
+ * One connector — the strip, its notice, and the Accounts / Tools / Settings
109
+ * tabs. The page (`/connectors/<app>/<connector>`) and the agent editor's
110
+ * connector modal render this same view, so the two cannot drift.
111
+ */
112
+ export function ConnectorDetailView({ projectId, connector, canWrite, onChanged, onRemoved, back, headerEnd, wrapTitle = (title) => title, onLeave, urlState = true, columnClassName = PAGE_COLUMN, }) {
103
113
  const t = useTranslations('connectorPages');
104
114
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
105
115
  const tSharing = useTranslations('accessSharing');
@@ -116,19 +126,22 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
116
126
  // The tab is in the URL, so a link opens the right one.
117
127
  const eventTriggers = useConnectorEventTriggers(projectId, connector);
118
128
  const tabs = connectorTabs(connector, { canWrite, hasEvents: eventTriggers.hasEvents });
119
- const requestedTab = search?.get('tab');
129
+ const [localTab, setLocalTab] = useState('accounts');
130
+ const requestedTab = urlState ? search?.get('tab') : localTab;
120
131
  const tab = tabs.find((value) => value === requestedTab) ?? tabs[0] ?? 'accounts';
121
- const setTab = (next) => replaceParams((params) => {
122
- if (next === 'accounts')
123
- params.delete('tab');
124
- else
125
- params.set('tab', next);
126
- });
132
+ const setTab = (next) => urlState
133
+ ? replaceParams((params) => {
134
+ if (next === 'accounts')
135
+ params.delete('tab');
136
+ else
137
+ params.set('tab', next);
138
+ })
139
+ : setLocalTab(next);
127
140
  // The install hand-off: `?connect=<connection id>` opens credential entry
128
141
  // for that account once, then leaves the URL. The id is untrusted, so
129
142
  // `connectHandoff` opens only one of THIS connector's accounts and takes the
130
143
  // owner from the row; the URL's `owner` param is stripped, never read.
131
- const connectId = search?.get('connect') ?? null;
144
+ const connectId = urlState ? (search?.get('connect') ?? null) : null;
132
145
  // Same cache entry as the probe in `useConnectorDetail`; read here for its
133
146
  // loading state, so a shared account is not refused before the answer lands.
134
147
  const accountId = useProjectAccountId(projectId);
@@ -195,7 +208,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
195
208
  };
196
209
  // Right after Install (`?add=1`): ask for the first account once, then
197
210
  // leave the URL. Adjusted during render, not in an effect.
198
- const addParam = search?.get('add') === '1';
211
+ const addParam = urlState && search?.get('add') === '1';
199
212
  const [addHandled, setAddHandled] = useState(false);
200
213
  if (addParam && !addHandled && canWrite && !isChannel) {
201
214
  setAddHandled(true);
@@ -301,7 +314,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
301
314
  setCredentialTarget(null);
302
315
  };
303
316
  const addAccountControl = isComputer ? (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: () => setComputerOpen(true), children: [_jsx(PlusIcon, { className: "size-4 shrink-0" }), tI18nComplete.raw('text1a2303ede074')] })) : (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: requestAdd, disabled: connectPending, children: [_jsx(PlusIcon, { className: "size-4 shrink-0" }), tSharing('addAccount')] }));
304
- return (_jsxs(SplitSheet, { open: sheetOpen, onOpenChange: closeCredential, size: "lg", className: "flex-1", children: [_jsx(SplitSheetMain, { children: _jsxs("div", { className: PAGE_COLUMN, children: [_jsx(Button, { asChild: true, variant: "text", size: "sm", className: "-ml-1 w-fit px-0 has-[>svg]:px-0", children: _jsxs(Link, { href: backHref, 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(ConnectorAppIcon, { connector: connector, size: "lg" }), _jsx("h1", { ref: headingRef, tabIndex: -1, className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: displayName })] }), _jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [isComputer && headerCta === 'connect' ? addAccountControl : null, connected ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => startPrivateSession(), children: t('newSession') })) : null] })] }), _jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [_jsx("dl", { className: "divide-border flex divide-x", children: stripCells.map((cell) => (_jsxs("div", { className: "min-w-0 flex-1 space-y-0.5 px-4 py-2.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { className: "truncate", children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground flex min-w-0 items-center gap-1.5 truncate text-sm font-medium", children: cell.value })] }, cell.key))) }), notice.kind !== 'none' ? (_jsxs(ConnectorNoticeRow, { title: t(NOTICE_TITLE[notice.kind], { name: displayName }), detail: notice.kind === 'error' && notice.reason ? (_jsx(NoticeLogLine, { reason: notice.reason })) : (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: t(notice.kind === 'needs_credential' && isManagedProvider
317
+ return (_jsxs(SplitSheet, { open: sheetOpen, onOpenChange: closeCredential, size: "lg", className: "flex-1", children: [_jsx(SplitSheetMain, { children: _jsxs("div", { className: columnClassName, children: [back, _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(ConnectorAppIcon, { connector: connector, size: "lg" }), wrapTitle(_jsx("h1", { ref: headingRef, tabIndex: -1, className: "text-foreground min-w-0 text-xl font-medium text-balance wrap-break-word", children: displayName }))] }), _jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [isComputer && headerCta === 'connect' ? addAccountControl : null, connected ? (_jsx(Button, { size: "sm", variant: "outline", onClick: () => startPrivateSession(), children: t('newSession') })) : null, headerEnd] })] }), _jsxs("div", { className: "bg-popover divide-border divide-y overflow-hidden rounded-md border", children: [_jsx("dl", { className: "divide-border flex divide-x", children: stripCells.map((cell) => (_jsxs("div", { className: "min-w-0 flex-1 space-y-0.5 px-4 py-2.5", children: [_jsxs("dt", { className: "text-muted-foreground flex h-4 items-center justify-between gap-2 text-xs", children: [_jsx("span", { className: "truncate", children: cell.label }), cell.labelAddon] }), _jsx("dd", { className: "text-foreground flex min-w-0 items-center gap-1.5 truncate text-sm font-medium", children: cell.value })] }, cell.key))) }), notice.kind !== 'none' ? (_jsxs(ConnectorNoticeRow, { title: t(NOTICE_TITLE[notice.kind], { name: displayName }), detail: notice.kind === 'error' && notice.reason ? (_jsx(NoticeLogLine, { reason: notice.reason })) : (_jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: t(notice.kind === 'needs_credential' && isManagedProvider
305
318
  ? 'noticeReconnectBody'
306
319
  : NOTICE_BODY[notice.kind], { name: displayName }) })), children: [notice.kind === 'error' && canWrite ? (_jsxs(Button, { size: "sm", variant: "ghost", className: "gap-1.5", onClick: () => retry.mutate(), disabled: retry.isPending, children: [retry.isPending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, tI18nComplete.raw('text942087cc2d41')] })) : null, fix?.kind === 'add_account' ? (addAccountControl) : fix?.kind === 'sign_in' ? (_jsxs(Button, { size: "sm", className: "gap-1.5", onClick: runFix, disabled: signIn.isPending, children: [signIn.isPending ? _jsx(SessionDotMatrix, { size: 14, className: "shrink-0" }) : null, t('noticeSignInAction')] })) : fix ? (
307
320
  // Fix it here: credential entry for a direct account, the
@@ -321,7 +334,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
321
334
  onStartSession: startPrivateSession,
322
335
  addRequest,
323
336
  onAddRequestHandled: () => setAddRequest(0),
324
- onRemoved: () => router.push(connectorsHref(projectId, 'connected')),
337
+ onRemoved: onLeave,
325
338
  onSetCredential: setCredentialTarget,
326
339
  onConnect: needsSignIn ? connectAccount : undefined,
327
340
  showAccountInfo: true,
@@ -331,7 +344,7 @@ function ConnectorPageBody({ projectId, connector, canWrite, backHref, onChanged
331
344
  displayName: displayName,
332
345
  onChanged: onChanged,
333
346
  onRemoved: () => {
334
- router.push(connectorsHref(projectId, 'connected'));
347
+ onLeave();
335
348
  onRemoved();
336
349
  },
337
350
  } })] })] }) }), credentialTarget ? (
@@ -217,6 +217,7 @@ export function ProviderAccordion({ projectId, canWrite = false, enabled = true,
217
217
  connectedIds,
218
218
  search,
219
219
  });
220
+ const disabledProviders = new Set(access.data?.enforced === true ? access.data.disabledProviders : []);
220
221
  const rows = ordered.map((entry) => {
221
222
  const method = providerMethod(entry);
222
223
  return {
@@ -233,7 +234,7 @@ export function ProviderAccordion({ projectId, canWrite = false, enabled = true,
233
234
  ? 'connected'
234
235
  : 'none',
235
236
  via: connectedVia(entry.id, connectedIds),
236
- disabled: access.data?.enforced === true && access.data.disabledProviders.includes(entry.id),
237
+ disabled: disabledProviders.has(entry.id),
237
238
  };
238
239
  });
239
240
  return {
@@ -235,6 +235,7 @@ export function ProviderConnect({ projectId, canWrite = false, enabled = true, s
235
235
  connectedIds,
236
236
  search,
237
237
  });
238
+ const disabledProviders = new Set(access.data?.enforced === true ? access.data.disabledProviders : []);
238
239
  const rows = ordered.map((entry) => {
239
240
  const method = providerMethod(entry);
240
241
  return {
@@ -252,7 +253,7 @@ export function ProviderConnect({ projectId, canWrite = false, enabled = true, s
252
253
  ? 'connected'
253
254
  : 'none',
254
255
  via: connectedVia(entry.id, connectedIds),
255
- disabled: access.data?.enforced === true && access.data.disabledProviders.includes(entry.id),
256
+ disabled: disabledProviders.has(entry.id),
256
257
  };
257
258
  });
258
259
  return {
@@ -56,7 +56,7 @@ function TelegramConnectForm({ projectId, onDone }) {
56
56
  function TelegramUsersForm({ projectId, initial, onDone, }) {
57
57
  const t = useTranslations('hardcodedUi.i18nComplete');
58
58
  const save = useSetTelegramAllowedUsers();
59
- const [users, setUsers] = useState(initial.join(', '));
59
+ const [users, setUsers] = useState(() => initial.join(', '));
60
60
  const ids = parseTelegramUserIds(users);
61
61
  return (_jsxs("form", { className: "space-y-4", onSubmit: (event) => {
62
62
  event.preventDefault();
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations as useI18nTranslations } from '../../i18n/index.js';
4
- import { useEffect, useState } from 'react';
4
+ import { useState } from 'react';
5
5
  import { Button } from '../../primitives/button.js';
6
6
  import { Disclosure, DisclosureContent, DisclosureTrigger } from '../../primitives/disclosure.js';
7
7
  import { Field, FieldDescription, FieldGroup, FieldLabel } from '../../primitives/field.js';
@@ -35,14 +35,16 @@ export function ConnectorConnectionModal({ open, idPrefix, title, initialName, i
35
35
  const [slug, setSlug] = useState(initialSlug);
36
36
  const [slugEdited, setSlugEdited] = useState(false);
37
37
  const [optionsOpen, setOptionsOpen] = useState(false);
38
- useEffect(() => {
39
- if (!open)
40
- return;
41
- setName(proposedName);
42
- setSlug(initialSlug);
43
- setSlugEdited(false);
44
- setOptionsOpen(false);
45
- }, [proposedName, initialSlug, open]);
38
+ const [resetFor, setResetFor] = useState(null);
39
+ if (!resetFor || resetFor[0] !== proposedName || resetFor[1] !== initialSlug || resetFor[2] !== open) {
40
+ setResetFor([proposedName, initialSlug, open]);
41
+ if (open) {
42
+ setName(proposedName);
43
+ setSlug(initialSlug);
44
+ setSlugEdited(false);
45
+ setOptionsOpen(false);
46
+ }
47
+ }
46
48
  const slugAvailable = isConnectorConnectionSlugAvailable(slug, existingSlugs);
47
49
  const slugInvalid = slug.length > 0 && !slugAvailable;
48
50
  const slugDescriptionId = `${idPrefix}-slug-description`;