@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
@@ -3,75 +3,79 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { uiRoutes } from '../../lib/routes.js';
4
4
  import { useTranslations as useI18nTranslations } from '../../i18n/index.js';
5
5
  /**
6
- * The agent editor's Access pages — Skills, Connectors, Secrets — built from
7
- * the SAME cards and the SAME modals as the project-wide tabs of the same
8
- * name (Marko, 2026-09-03: "hardcore reuse same components as much as
9
- * possible & same modals/dialogues"). A card here is a `CatalogCard` in
10
- * select mode: its checkbox says whether the agent is granted the thing, its
11
- * body opens the thing itself — `EntityDetailModal` for a skill,
12
- * `ConnectorModal` for a connector, `ProjectSecretDialog` for a secret.
6
+ * The agent editor's Access pages — Skills, Connectors, Secrets, Apps. Each
7
+ * opens the SAME modal as the project-wide tab of the same name:
8
+ * `EntityDetailModal` for a skill, `ConnectorModal` for a connector,
9
+ * `ProjectSecretDialog` for a secret.
13
10
  *
14
- * The grant itself is still the All · Pick · None machine from
15
- * `grant-mode-field.tsx`; the catalog stays on screen in every mode so All
16
- * reads as "every card checked" and None as "every card unchecked".
11
+ * Layout is the Settings page's: the pane title, one All · Pick · None row
12
+ * (`grant-mode-field.tsx`), then the catalog as one bordered list. The list
13
+ * stays on screen in every mode, so a person can still open any entry.
17
14
  */
18
15
  import { Badge } from '../../primitives/badge.js';
19
16
  import { Button } from '../../primitives/button.js';
17
+ import { Checkbox } from '../../primitives/checkbox.js';
18
+ import { Skeleton } from '../../primitives/skeleton.js';
20
19
  import { EmptyState } from '../../primitives/empty-state.js';
21
20
  import { ErrorState } from '../../primitives/error-state.js';
22
21
  import { connectorDisplayName } from '../connectors/connector-filter.js';
23
- import { ConnectorAppIcon, ConnectorConnectedMark, ConnectorStatusBadge, } from '../connectors/connector-identity.js';
22
+ import { ConnectorAppIcon, ConnectorStatusBadge, } from '../connectors/connector-identity.js';
24
23
  import { ConnectorModal } from '../connectors/detail/connector-modal.js';
25
24
  import { providerLabel } from '../connectors/provider-label.js';
26
- import { CatalogCard } from '../shared/catalog/catalog-card.js';
27
- import { CatalogGridSkeleton } from '../shared/catalog/catalog-grid.js';
28
25
  import { detailSelection } from '../shared/detail-selection.js';
29
26
  import { EntityDetailModal } from '../shared/entity/entity-modal.js';
30
- import { connectorSetupStatus } from '../sections/connector-connection-form.js';
31
- import { GrantHeaderTrailing, RequiredConnectorToggle, } from '../sections/view/agent-editor-access-fields.js';
32
- import { EditorSection } from '../sections/view/agent-editor-primitives.js';
27
+ import { ManageButton, RequiredConnectorToggle, } from '../sections/view/agent-editor-access-fields.js';
28
+ import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
33
29
  import { GrantModeField } from '../sections/view/grant-mode-field.js';
34
30
  import { buildRows, ProjectSecretDialog, } from '../sections/view/secrets-view.js';
35
31
  import { toArray } from '../shared/utils.js';
36
32
  import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
33
+ import { cn } from '../../internal/lib/utils.js';
37
34
  import { useProjectCan } from '../../internal/lib/use-project-can.js';
38
35
  import { listConnectors, listProjectSecrets, } from '@kortix/sdk';
39
36
  import { contract, qk, useProjectAccountId, useProjectApps, } from '@kortix/sdk/react';
40
- import { GlobeIcon, KeyIcon, PlusIcon, SparkleIcon } from '@phosphor-icons/react';
37
+ import { PlusIcon } from '@phosphor-icons/react';
41
38
  import { useQuery, useQueryClient } from '@tanstack/react-query';
42
- import { useEffect, useState } from 'react';
39
+ import { useState } from 'react';
43
40
  import { appGrantRows } from './agent-apps-grant.js';
44
41
  /**
45
- * All · Pick · None over a grid of selectable cards. `items` is the project's
46
- * catalog; a granted id the catalog no longer has still renders, flagged, so
47
- * a stale grant is visible rather than silently dropped.
42
+ * All · Pick · None over the project's catalog, as one bordered list. The
43
+ * checkbox exists only in Pick mode, where it is the control; in All and
44
+ * None the mode row above already answers for every row. A granted id the
45
+ * catalog no longer has still renders, flagged, so a stale grant is visible
46
+ * rather than silently dropped.
48
47
  */
49
- function GrantCatalog({ value, onChange, allLabel, items, isLoading, isError, onRetry, empty, trailingFor, }) {
48
+ function GrantCatalog({ value, onChange, items, isLoading, isError, onRetry, empty, trailingFor, }) {
50
49
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
51
- return (_jsx(GrantModeField, { value: value, onChange: onChange, allLabel: allLabel, noneLabel: tI18nComplete.raw('text765b240c2900'), alwaysRender: true, children: ({ selected, toggle, mode }) => {
52
- if (isLoading)
53
- return _jsx(CatalogGridSkeleton, {});
50
+ return (_jsx(GrantModeField, { value: value, onChange: onChange, alwaysRender: true, children: ({ selected, toggle, mode }) => {
51
+ if (isLoading) {
52
+ return (_jsx(EditorList, { children: [0, 1, 2].map((i) => (_jsx("li", { className: "flex items-center gap-3 px-4 py-3", children: _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" }) }, i))) }));
53
+ }
54
54
  if (isError) {
55
55
  return (_jsx(ErrorState, { size: "sm", title: tI18nComplete.raw('text3f2d97c61a7e'), action: onRetry ? (_jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: tI18nComplete.raw('text942087cc2d41') })) : undefined }));
56
56
  }
57
57
  const known = new Set(items.map((i) => i.id));
58
- const orphans = [...selected].filter((id) => !known.has(id));
58
+ const orphans = [...selected]
59
+ .filter((id) => !known.has(id))
60
+ .map((id) => ({
61
+ id,
62
+ title: _jsx("span", { className: "font-mono", children: id }),
63
+ description: tI18nComplete.raw('textfecc36d012be'),
64
+ status: (_jsx(Badge, { variant: "destructive", size: "xs", children: tI18nComplete.raw('text6be36ca49ee8') })),
65
+ }));
59
66
  if (items.length === 0 && orphans.length === 0)
60
67
  return _jsx(_Fragment, { children: empty });
61
- const checked = (id) => mode === 'all' || (mode === 'pick' && selected.has(id));
62
- return (_jsxs("div", { className: "grid gap-2", children: [items.map((item) => (_jsx(CatalogCard, { ...item.card, trailing: _jsxs("span", { className: "flex items-center gap-1.5", children: [item.card.trailing, mode === 'pick' && selected.has(item.id) ? trailingFor?.(item.id) : null] }), onClick: item.onOpen, select: {
63
- checked: checked(item.id),
64
- onCheckedChange: () => toggle(item.id),
65
- disabled: mode !== 'pick',
66
- label: tI18nComplete('textd4170e74c3b0', { value0: item.id }),
67
- } }, item.id))), orphans.map((id) => (_jsx(CatalogCard, { title: _jsx("span", { className: "font-mono", children: id }), description: tI18nComplete.raw('textfecc36d012be'), badges: _jsx(Badge, { variant: "destructive", size: "xs", children: tI18nComplete.raw('text6be36ca49ee8') }), select: {
68
- checked: true,
69
- onCheckedChange: () => toggle(id),
70
- disabled: mode !== 'pick',
71
- label: tI18nComplete('textd4170e74c3b0', { value0: id }),
72
- } }, id)))] }));
68
+ return (_jsx(EditorList, { children: [...items, ...orphans].map((item) => (_jsx(GrantRow, { item: item, mode: mode, checked: selected.has(item.id), onToggle: () => toggle(item.id), checkLabel: tI18nComplete('textd4170e74c3b0', { value0: item.id }), trailing: mode === 'pick' && selected.has(item.id) ? trailingFor?.(item.id) : null }, item.id))) }));
73
69
  } }));
74
70
  }
71
+ /**
72
+ * One catalog row. The checkbox and the body are two sibling controls, never
73
+ * one nested in the other: the checkbox grants, the body opens the entity.
74
+ */
75
+ function GrantRow({ item, mode, checked, onToggle, checkLabel, trailing, }) {
76
+ const body = (_jsxs(_Fragment, { children: [item.leading, _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: item.title }), item.description ? (_jsx("span", { className: "text-muted-foreground block truncate text-xs", children: item.description })) : null] })] }));
77
+ return (_jsxs("li", { className: cn('flex min-h-13 items-center gap-3 px-4 py-2 transition-colors duration-fast', item.onOpen && 'hover:bg-hover', mode === 'none' && 'opacity-60'), children: [mode === 'pick' ? (_jsx(Checkbox, { variant: "black", checked: checked, onCheckedChange: onToggle, "aria-label": checkLabel })) : null, item.onOpen ? (_jsx("button", { type: "button", onClick: item.onOpen, className: "focus-visible:ring-ring/50 flex min-w-0 flex-1 items-center gap-3 rounded-sm text-left focus-visible:ring-2 focus-visible:outline-none", children: body })) : (_jsx("span", { className: "flex min-w-0 flex-1 items-center gap-3", children: body })), item.status || trailing ? (_jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [item.status, trailing] })) : null] }));
78
+ }
75
79
  // ─── Skills ──────────────────────────────────────────────────────────────
76
80
  export function SkillsGrantPage({ projectId, config, editor, }) {
77
81
  const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
@@ -82,11 +86,12 @@ export function SkillsGrantPage({ projectId, config, editor, }) {
82
86
  record: skills.find((skill) => skill.path === selectedPath),
83
87
  isSuccess: true,
84
88
  });
85
- return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('text66d0f523a379'), description: tI18nComplete.raw('text836cd8e8a791'), trailing: _jsx(GrantHeaderTrailing, { value: editor.draft.skills, tab: "skills", label: tI18nComplete.raw('text78abcbbfb830') }), children: _jsx("div", { className: "py-4", children: _jsx(GrantCatalog, { value: editor.draft.skills, onChange: (v) => editor.set('skills', v), allLabel: tI18nComplete.raw('textf059c2dbe260'), items: skills.map((skill) => ({
86
- id: skill.name,
87
- card: { title: skill.name, description: skill.description },
88
- onOpen: () => setSelectedPath(skill.path),
89
- })), empty: _jsx(EmptyState, { icon: SparkleIcon, size: "sm", title: tI18nComplete.raw('text32ae9b80f832'), description: tI18nComplete.raw('text62d220bbfcc8') }) }) }) }), _jsx(EntityDetailModal, { projectId: projectId, entity: detail.record, kind: "skill", open: detail.open, isResolving: detail.isResolving, onOpenChange: (next) => {
89
+ return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('text66d0f523a379'), description: tI18nComplete.raw('text836cd8e8a791'), trailing: _jsx(ManageButton, { tab: "skills" }), lead: true, bare: true, children: _jsx(GrantCatalog, { value: editor.draft.skills, onChange: (v) => editor.set('skills', v), items: skills.map((skill) => ({
90
+ id: skill.name,
91
+ title: skill.name,
92
+ description: skill.description,
93
+ onOpen: () => setSelectedPath(skill.path),
94
+ })), empty: _jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('text32ae9b80f832'), description: tI18nComplete.raw('text62d220bbfcc8') }) }) }), _jsx(EntityDetailModal, { projectId: projectId, entity: detail.record, kind: "skill", open: detail.open, isResolving: detail.isResolving, onOpenChange: (next) => {
90
95
  if (!next)
91
96
  setSelectedPath(null);
92
97
  } })] }));
@@ -110,30 +115,26 @@ export function ConnectorsGrantPage({ projectId, editor, }) {
110
115
  record: connectors.find((c) => c.slug === detailSlug),
111
116
  isSuccess: connectorsQuery.isSuccess,
112
117
  });
113
- useEffect(() => {
114
- if (detail.isMissing)
115
- setDetailSlug(null);
116
- }, [detail.isMissing]);
118
+ if (detail.isMissing)
119
+ setDetailSlug(null);
117
120
  const invalidate = () => queryClient.invalidateQueries({ queryKey: qk.project.connectors(projectId) });
118
121
  const { draft, set } = editor;
119
- return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('textc3d2e79ebdd0'), description: tI18nComplete.raw('textc174fc077197'), trailing: _jsx(GrantHeaderTrailing, { value: draft.connectors, tab: "connectors", label: tI18nComplete.raw('textd83250185d41') }), children: _jsxs("div", { className: "space-y-2 py-4", children: [_jsx(GrantCatalog, { value: draft.connectors, onChange: (v) => {
122
+ return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('textc3d2e79ebdd0'), description: tI18nComplete.raw('textc174fc077197'), trailing: _jsx(ManageButton, { tab: "connectors" }), lead: true, bare: true, children: _jsxs("div", { className: "space-y-3", children: [_jsx(GrantCatalog, { value: draft.connectors, onChange: (v) => {
120
123
  set('connectors', v);
121
124
  const required = draft.connectors_required ?? [];
125
+ const picked = new Set(Array.isArray(v) ? v : []);
122
126
  const kept = v === 'all' || v === 'none' || !Array.isArray(v)
123
127
  ? []
124
- : required.filter((slug) => v.includes(slug));
128
+ : required.filter((slug) => picked.has(slug));
125
129
  if (kept.length !== required.length) {
126
130
  set('connectors_required', kept.length ? kept : undefined);
127
131
  }
128
- }, allLabel: tI18nComplete.raw('text35e54cea7f41'), isLoading: connectorsQuery.isLoading, isError: connectorsQuery.isError, onRetry: () => void connectorsQuery.refetch(), items: connectors.map((connector) => ({
132
+ }, isLoading: connectorsQuery.isLoading, isError: connectorsQuery.isError, onRetry: () => void connectorsQuery.refetch(), items: connectors.map((connector) => ({
129
133
  id: connector.slug,
130
- card: {
131
- leading: _jsx(ConnectorAppIcon, { connector: connector, size: "lg" }),
132
- title: connectorDisplayName(connector),
133
- description: `${providerLabel(connector.provider)} · ${connector.actions.length} ${connector.actions.length === 1 ? 'action' : 'actions'}`,
134
- badges: _jsx(ConnectorStatusBadge, { connector: connector }),
135
- trailing: connectorSetupStatus(connector) === 'connected' ? (_jsx(ConnectorConnectedMark, {})) : undefined,
136
- },
134
+ leading: _jsx(ConnectorAppIcon, { connector: connector, size: "sm" }),
135
+ title: connectorDisplayName(connector),
136
+ description: `${providerLabel(connector.provider)} · ${connector.actions.length} ${connector.actions.length === 1 ? 'action' : 'actions'}`,
137
+ status: _jsx(ConnectorStatusBadge, { connector: connector }),
137
138
  onOpen: () => setDetailSlug(connector.slug),
138
139
  })), trailingFor: (slug) => (_jsx(RequiredConnectorToggle, { active: draft.connectors_required?.includes(slug) === true, onToggle: () => {
139
140
  const current = draft.connectors_required ?? [];
@@ -161,16 +162,13 @@ export function SecretsGrantPage({ projectId, editor }) {
161
162
  open: false,
162
163
  row: null,
163
164
  });
164
- return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('textd8707d411d99'), description: tI18nComplete.raw('textbb74cc7dce98'), trailing: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(GrantHeaderTrailing, { value: editor.draft.secrets, tab: "secrets", label: tI18nComplete.raw('text0b38e3daeb93') }), canWrite ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setDialog({ open: true, row: null }), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text18fdd549b2ed')] })) : null] }), children: _jsx("div", { className: "py-4", children: _jsx(GrantCatalog, { value: editor.draft.secrets, onChange: (v) => editor.set('secrets', v), allLabel: tI18nComplete.raw('text86fdc0c950d5'), isLoading: secretsQuery.isLoading, isError: secretsQuery.isError, onRetry: () => void secretsQuery.refetch(), items: rows.map((row) => ({
165
- id: row.identifier,
166
- card: {
167
- leading: (_jsx("span", { className: "bg-muted text-muted-foreground flex size-9 items-center justify-center rounded-md", children: _jsx(KeyIcon, { className: "size-4" }) })),
168
- title: _jsx("span", { className: "font-mono", children: row.identifier }),
169
- description: row.purpose ?? (row.key !== row.identifier ? `Env var ${row.key}` : null),
170
- badges: (_jsxs(_Fragment, { children: [!row.configured ? (_jsx(Badge, { variant: "destructive", size: "xs", children: tI18nComplete.raw('text4fd0272a5628') })) : null, row.system ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('text6725e7bbcd28') })) : null] })),
171
- },
172
- onOpen: () => setDialog({ open: true, row }),
173
- })), empty: _jsx(EmptyState, { icon: KeyIcon, size: "sm", title: tI18nComplete.raw('text5aa40906d187'), description: tI18nComplete.raw('textfc7605717e58'), action: canWrite ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => setDialog({ open: true, row: null }), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text98aa52b34c8f')] })) : undefined }) }) }) }), _jsx(ProjectSecretDialog, { projectId: projectId, row: dialog.row, open: dialog.open, onOpenChange: (open) => setDialog((d) => ({ ...d, open })) })] }));
165
+ return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('textd8707d411d99'), description: tI18nComplete.raw('textbb74cc7dce98'), trailing: canWrite ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setDialog({ open: true, row: null }), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text18fdd549b2ed')] })) : undefined, lead: true, bare: true, children: _jsx(GrantCatalog, { value: editor.draft.secrets, onChange: (v) => editor.set('secrets', v), isLoading: secretsQuery.isLoading, isError: secretsQuery.isError, onRetry: () => void secretsQuery.refetch(), items: rows.map((row) => ({
166
+ id: row.identifier,
167
+ title: _jsx("span", { className: "font-mono", children: row.identifier }),
168
+ description: row.purpose ?? (row.key !== row.identifier ? `Env var ${row.key}` : null),
169
+ status: !row.configured || row.system ? (_jsxs(_Fragment, { children: [!row.configured ? (_jsx(Badge, { variant: "destructive", size: "xs", children: tI18nComplete.raw('text4fd0272a5628') })) : null, row.system ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('text6725e7bbcd28') })) : null] })) : undefined,
170
+ onOpen: () => setDialog({ open: true, row }),
171
+ })), empty: _jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('text5aa40906d187'), description: tI18nComplete.raw('textfc7605717e58'), action: canWrite ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => setDialog({ open: true, row: null }), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text98aa52b34c8f')] })) : undefined }) }) }), _jsx(ProjectSecretDialog, { projectId: projectId, row: dialog.row, open: dialog.open, onOpenChange: (open) => setDialog((d) => ({ ...d, open })) })] }));
174
172
  }
175
173
  // ─── Apps ────────────────────────────────────────────────────────────────
176
174
  /**
@@ -191,13 +189,12 @@ export function AppsGrantPage({ projectId, editor }) {
191
189
  const t = useI18nTranslations('agentApps');
192
190
  const appsQuery = useProjectApps(projectId);
193
191
  const rows = appGrantRows(appsQuery.data);
194
- return (_jsx(EditorSection, { title: t('title'), description: t('description'), trailing: _jsx(GrantHeaderTrailing, { value: editor.draft.apps, href: (id) => uiRoutes().apps(id), label: t('manageApps') }), children: _jsxs("div", { className: "space-y-3 py-4", "data-testid": "agent-apps-grant", children: [_jsx(GrantCatalog, { value: editor.draft.apps, onChange: (v) => editor.set('apps', v), allLabel: t('allLabel'), isLoading: appsQuery.isLoading, isError: appsQuery.isError, onRetry: () => void appsQuery.refetch(), items: rows.map((row) => ({
195
- id: row.id,
196
- card: {
197
- leading: (_jsx("span", { className: "bg-muted text-muted-foreground flex size-9 items-center justify-center rounded-md", children: _jsx(GlobeIcon, { className: "size-4" }) })),
198
- title: _jsx("span", { className: "font-mono", children: row.id }),
199
- description: row.name === row.id ? null : row.name,
200
- badges: row.needsGrant ? (_jsx(Badge, { variant: "outline", size: "xs", children: t('needsGrant') })) : (_jsx(Badge, { variant: "muted", size: "xs", children: t('openWithoutGrant') })),
201
- },
202
- })), empty: _jsx(EmptyState, { icon: GlobeIcon, size: "sm", title: t('emptyTitle'), description: t('emptyDescription') }) }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: t('grantNote') })] }) }));
192
+ return (_jsx(EditorSection, { title: t('title'), description: t('description'), trailing: _jsx(ManageButton, { href: (id) => uiRoutes().apps(id) }), lead: true, bare: true, children: _jsx("div", { className: "space-y-3", "data-testid": "agent-apps-grant", children: _jsx(GrantCatalog, { value: editor.draft.apps, onChange: (v) => editor.set('apps', v), isLoading: appsQuery.isLoading, isError: appsQuery.isError, onRetry: () => void appsQuery.refetch(), items: rows.map((row) => ({
193
+ id: row.id,
194
+ title: _jsx("span", { className: "font-mono", children: row.id }),
195
+ description: row.name === row.id ? null : row.name,
196
+ // The grant decides admission for a restricted or private App, the
197
+ // normal case. Only an App that admits agents without it is flagged.
198
+ status: row.needsGrant ? undefined : (_jsx(Badge, { variant: "muted", size: "xs", children: t('openWithoutGrant') })),
199
+ })), empty: _jsx(EmptyState, { size: "sm", title: t('emptyTitle'), description: t('emptyDescription') }) }) }) }));
203
200
  }
@@ -0,0 +1,35 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare const PROTOTYPE_ENABLED: boolean;
3
+ export declare const NAV_VARIANTS: readonly [{
4
+ readonly key: "rail";
5
+ }, {
6
+ readonly key: "tabs";
7
+ }, {
8
+ readonly key: "groups";
9
+ }, {
10
+ readonly key: "inline";
11
+ }];
12
+ export type NavVariant = (typeof NAV_VARIANTS)[number]['key'];
13
+ export declare function useNavVariant(): NavVariant;
14
+ export interface NavItem {
15
+ key: string;
16
+ label: string;
17
+ group: string;
18
+ }
19
+ interface NavProps {
20
+ groups: {
21
+ group: string;
22
+ items: NavItem[];
23
+ }[];
24
+ section: string;
25
+ href: (key: string) => string;
26
+ /** The current value per topic — "All", "2 picked", the model name. */
27
+ summaries?: Partial<Record<string, string>>;
28
+ }
29
+ export declare function GroupedUnderlineTabs({ groups, section, href }: NavProps): import("react").JSX.Element;
30
+ export declare function GroupSwitchTabs({ groups, section, href }: NavProps): import("react").JSX.Element;
31
+ export declare function InlineVerticalTabs({ groups, section, href, summaries, children, }: NavProps & {
32
+ children: ReactNode;
33
+ }): import("react").JSX.Element;
34
+ export declare function PrototypeSwitcher(): import("react").JSX.Element | null;
35
+ export {};
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * PROTOTYPE — throwaway. Answers one question: what replaces the agent page's
5
+ * left rail? Rendered on the real agent page, switched with `?variant=`:
6
+ *
7
+ * rail — today's left rail (the baseline; no param)
8
+ * tabs — grouped underline tabs under the header, no rail
9
+ * groups — General / Access / Runtime switch + that group's tabs
10
+ * inline — vertical tabs beside the content column, each with its value
11
+ *
12
+ * Read-only navigation over the same `?section=` links; nothing here mutates.
13
+ * Once a variant wins, fold it into `agent-page.tsx` properly and delete this
14
+ * file and the switcher. Dev builds only (`PROTOTYPE_ENABLED`).
15
+ */
16
+ import { CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react';
17
+ import { useCallback, useEffect } from 'react';
18
+ import { useTranslations } from '../../i18n/index.js';
19
+ import { cn } from '../../internal/lib/utils.js';
20
+ import { Tabs, TabsList, TabsTrigger } from '../../primitives/tabs.js';
21
+ import { UiLink as Link, useUiPathname as usePathname, useUiRouter as useRouter, useUiSearchParams as useSearchParams, } from '../../providers/ui-provider.js';
22
+ export const PROTOTYPE_ENABLED = process.env.NODE_ENV !== 'production';
23
+ export const NAV_VARIANTS = [{ key: 'rail' }, { key: 'tabs' }, { key: 'groups' }, { key: 'inline' }];
24
+ export function useNavVariant() {
25
+ const raw = useSearchParams().get('variant');
26
+ if (!PROTOTYPE_ENABLED)
27
+ return 'rail';
28
+ return NAV_VARIANTS.some((v) => v.key === raw) ? raw : 'rail';
29
+ }
30
+ /** `?section=` link that keeps `?variant=` so switching topics stays in the variant. */
31
+ function useVariantHref(href) {
32
+ const variant = useSearchParams().get('variant');
33
+ return (key) => (variant ? `${href(key)}&variant=${variant}` : href(key));
34
+ }
35
+ // ─── tabs: grouped underline tabs ──────────────────────────────────────────
36
+ export function GroupedUnderlineTabs({ groups, section, href }) {
37
+ const to = useVariantHref(href);
38
+ return (_jsx("nav", { className: "border-border/60 flex shrink-0 items-end gap-8 overflow-x-auto border-b px-5 pt-1", children: groups.map((g) => (_jsxs("div", { className: "flex shrink-0 flex-col gap-1", children: [_jsx("span", { className: "text-muted-foreground px-2 text-xs", children: g.group }), _jsx(Tabs, { value: section, children: _jsx(TabsList, { type: "underline", className: "h-auto gap-1 border-b-0", children: g.items.map((item) => (_jsx(TabsTrigger, { value: item.key, asChild: true, className: "w-fit flex-none px-2", children: _jsx(Link, { href: to(item.key), prefetch: true, children: item.label }) }, item.key))) }) })] }, g.group))) }));
39
+ }
40
+ // ─── groups: group switch + sub tabs ───────────────────────────────────────
41
+ export function GroupSwitchTabs({ groups, section, href }) {
42
+ const to = useVariantHref(href);
43
+ const active = groups.find((g) => g.items.some((i) => i.key === section)) ?? groups[0];
44
+ return (_jsxs("nav", { className: "border-border/60 flex shrink-0 items-center gap-5 overflow-x-auto border-b px-5 py-2", children: [_jsx(Tabs, { value: active?.group, children: _jsx(TabsList, { children: groups.map((g) => (_jsx(TabsTrigger, { value: g.group, asChild: true, className: "w-fit flex-none", children: _jsx(Link, { href: to(g.items[0].key), prefetch: true, children: g.group }) }, g.group))) }) }), _jsx(Tabs, { value: section, children: _jsx(TabsList, { type: "underline", className: "h-auto gap-1 border-b-0", children: active?.items.map((item) => (_jsx(TabsTrigger, { value: item.key, asChild: true, className: "w-fit flex-none px-2", children: _jsx(Link, { href: to(item.key), prefetch: true, children: item.label }) }, item.key))) }) })] }));
45
+ }
46
+ // ─── inline: vertical tabs beside the content, with values ────────────────
47
+ export function InlineVerticalTabs({ groups, section, href, summaries, children, }) {
48
+ const to = useVariantHref(href);
49
+ return (_jsxs("div", { className: "mx-auto flex w-full max-w-5xl items-start gap-14 px-4 py-10 pb-32 lg:py-14", children: [_jsx("nav", { className: "sticky top-0 flex w-52 shrink-0 flex-col gap-4", children: groups.map((g) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-muted-foreground px-2.5 pb-1 text-xs", children: g.group }), g.items.map((item) => {
50
+ const isActive = item.key === section;
51
+ return (_jsxs(Link, { href: to(item.key), prefetch: true, "aria-current": isActive ? 'page' : undefined, className: cn('flex items-center justify-between gap-3 rounded-md px-2.5 py-1.5 text-sm transition-none', isActive
52
+ ? 'bg-active text-foreground font-medium'
53
+ : 'text-muted-foreground hover:bg-hover hover:text-foreground'), children: [_jsx("span", { className: "truncate", children: item.label }), summaries?.[item.key] ? (_jsx("span", { className: "text-muted-foreground shrink-0 text-xs font-normal tabular-nums", children: summaries[item.key] })) : null] }, item.key));
54
+ })] }, g.group))) }), _jsx("div", { className: "min-w-0 max-w-2xl flex-1", children: children })] }));
55
+ }
56
+ // ─── the floating switcher ────────────────────────────────────────────────
57
+ export function PrototypeSwitcher() {
58
+ const router = useRouter();
59
+ const pathname = usePathname();
60
+ const search = useSearchParams();
61
+ const current = useNavVariant();
62
+ const t = useTranslations('agentNavPrototype');
63
+ const labels = {
64
+ rail: t('variantRail'),
65
+ tabs: t('variantTabs'),
66
+ groups: t('variantGroups'),
67
+ inline: t('variantInline'),
68
+ };
69
+ const index = NAV_VARIANTS.findIndex((v) => v.key === current);
70
+ const go = useCallback((step) => {
71
+ const next = NAV_VARIANTS[(index + step + NAV_VARIANTS.length) % NAV_VARIANTS.length];
72
+ const params = new URLSearchParams(search.toString());
73
+ if (next.key === 'rail')
74
+ params.delete('variant');
75
+ else
76
+ params.set('variant', next.key);
77
+ router.replace(`${pathname}?${params.toString()}`, { scroll: false });
78
+ }, [index, pathname, router, search]);
79
+ useEffect(() => {
80
+ const onKey = (event) => {
81
+ const target = event.target;
82
+ if (target?.closest('input, textarea, select, [contenteditable="true"]'))
83
+ return;
84
+ if (event.key === 'ArrowLeft')
85
+ go(-1);
86
+ if (event.key === 'ArrowRight')
87
+ go(1);
88
+ };
89
+ window.addEventListener('keydown', onKey);
90
+ return () => window.removeEventListener('keydown', onKey);
91
+ }, [go]);
92
+ if (!PROTOTYPE_ENABLED)
93
+ return null;
94
+ return (_jsx("div", { className: "pointer-events-none fixed inset-x-0 bottom-20 z-50 flex justify-center", children: _jsxs("div", { className: "bg-foreground text-background pointer-events-auto flex items-center gap-1 rounded-full p-1 shadow-lg", children: [_jsx("button", { type: "button", "aria-label": t('previousVariant'), onClick: () => go(-1), className: "flex size-7 items-center justify-center rounded-full hover:bg-background/15", children: _jsx(CaretLeftIcon, { className: "size-3.5" }) }), _jsxs("span", { className: "min-w-56 px-2 text-center text-xs tabular-nums", children: [String.fromCharCode(65 + index), " \u00B7 ", labels[NAV_VARIANTS[index].key]] }), _jsx("button", { type: "button", "aria-label": t('nextVariant'), onClick: () => go(1), className: "flex size-7 items-center justify-center rounded-full hover:bg-background/15", children: _jsx(CaretRightIcon, { className: "size-3.5" }) })] }) }));
95
+ }