@kortix/ui 0.14.9 → 0.14.10

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 (72) hide show
  1. package/dist/_vendor/kortix-shared.js +55 -2
  2. package/dist/_vendor/shared/artifact-comments.d.ts +235 -0
  3. package/dist/_vendor/shared/channel-descriptors.d.ts +96 -0
  4. package/dist/_vendor/shared/channel-message.d.ts +13 -2
  5. package/dist/_vendor/shared/index.d.ts +1 -0
  6. package/dist/apps/app-preview-pool.js +19 -2
  7. package/dist/artifacts/artifact-frame.js +7 -3
  8. package/dist/customize/sections/component/channel-access-panel.js +4 -4
  9. package/dist/customize/sections/component/telegram-channel-row.js +7 -5
  10. package/dist/customize/sections/view/agent-editor-catalog.js +5 -0
  11. package/dist/customize/sections/view/channels-view.d.ts +2 -1
  12. package/dist/customize/sections/view/channels-view.js +12 -7
  13. package/dist/customize/settings/rail.js +9 -0
  14. package/dist/customize/settings/settings-tabs.d.ts +1 -1
  15. package/dist/customize/settings/settings-tabs.js +1 -0
  16. package/dist/customize/settings/tabs/chrome-connections-tab.d.ts +6 -0
  17. package/dist/customize/settings/tabs/chrome-connections-tab.js +99 -0
  18. package/dist/customize/shared/catalog/catalog-empty-state.d.ts +3 -3
  19. package/dist/customize/shared/catalog/catalog-empty-state.js +6 -4
  20. package/dist/files/renderers/image-frame.d.ts +3 -1
  21. package/dist/files/renderers/image-frame.js +2 -2
  22. package/dist/i18n/en.d.ts +189 -0
  23. package/dist/i18n/en.js +190 -1
  24. package/dist/internal/apps-and-extensions/apps-modal.d.ts +9 -0
  25. package/dist/internal/apps-and-extensions/apps-modal.js +107 -0
  26. package/dist/internal/chrome/browser-icon.d.ts +24 -0
  27. package/dist/internal/chrome/browser-icon.js +58 -0
  28. package/dist/internal/chrome/chrome-card-state.d.ts +16 -0
  29. package/dist/internal/chrome/chrome-card-state.js +9 -0
  30. package/dist/internal/chrome/chrome-connect.d.ts +69 -0
  31. package/dist/internal/chrome/chrome-connect.js +178 -0
  32. package/dist/internal/chrome/chrome-mock.d.ts +4 -0
  33. package/dist/internal/chrome/chrome-mock.js +15 -0
  34. package/dist/internal/chrome/chrome-names.d.ts +15 -0
  35. package/dist/internal/chrome/chrome-names.js +35 -0
  36. package/dist/internal/chrome/extension.d.ts +47 -0
  37. package/dist/internal/chrome/extension.js +91 -0
  38. package/dist/internal/chrome/use-chrome-offered.d.ts +11 -0
  39. package/dist/internal/chrome/use-chrome-offered.js +22 -0
  40. package/dist/internal/connector-logo/connector-logo-source.d.ts +2 -1
  41. package/dist/internal/connector-logo/connector-logo-source.js +7 -6
  42. package/dist/internal/icons/edge.d.ts +5 -0
  43. package/dist/internal/icons/edge.js +10 -0
  44. package/dist/internal/lib/browser-noise/rules/extensions.js +17 -3
  45. package/dist/internal/lib/detect-os.d.ts +43 -0
  46. package/dist/internal/lib/detect-os.js +70 -0
  47. package/dist/internal/lib/use-project-feature-flags.js +5 -1
  48. package/dist/internal/stores/apps-modal-store.d.ts +8 -0
  49. package/dist/internal/stores/apps-modal-store.js +6 -0
  50. package/dist/internal/stores/composer-prefill-store.d.ts +12 -5
  51. package/dist/markdown/setup-links/approval-decision.js +3 -1
  52. package/dist/primitives/empty-state.d.ts +2 -2
  53. package/dist/primitives/empty-state.js +6 -5
  54. package/dist/primitives/mermaid-renderer.js +1 -1
  55. package/dist/session/panel/session-audit-shared.js +3 -1
  56. package/dist/session/participants/session-participants.d.ts +3 -1
  57. package/dist/session/participants/session-participants.js +2 -2
  58. package/dist/session/prompts/approval-request.js +2 -0
  59. package/dist/session/thread/session-thread.d.ts +13 -1
  60. package/dist/session/thread/session-thread.js +15 -5
  61. package/dist/session/thread/session-transcript-class.d.ts +5 -1
  62. package/dist/session/thread/session-transcript-class.js +5 -1
  63. package/dist/session/tool/tools/image-gen-tool.js +4 -3
  64. package/dist/session/turn/user-message.d.ts +7 -5
  65. package/dist/session/turn/user-message.js +14 -8
  66. package/dist/styles.css +1 -1
  67. package/dist/workspace/sessions/session-label-badges.js +2 -1
  68. package/dist/workspace/sidebar/footer/project-chrome-nav.d.ts +10 -0
  69. package/dist/workspace/sidebar/footer/project-chrome-nav.js +56 -0
  70. package/dist/workspace/sidebar/project-sidebar.js +5 -1
  71. package/package.json +43 -3
  72. package/skills/kortix-ui/references/modules.md +1 -1
@@ -108,6 +108,7 @@ import { useEffect, useMemo, useState } from 'react';
108
108
  import { useTunnelConnections } from '@kortix/sdk/react';
109
109
  import { ComputerHero } from '../../connectors/detail/computer-app-page.js';
110
110
  import { FeatureGateScreen } from '../../shared/feature-gate-screen.js';
111
+ import { CHANNEL_PLATFORM_IDS, CHANNELS, isChannelPlatform, } from '../../../_vendor/kortix-shared.js';
111
112
  export function ChannelsSection({ projectId, platform }) {
112
113
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
113
114
  useTeamsInstallReturnToast();
@@ -137,7 +138,7 @@ export function ChannelsSection({ projectId, platform }) {
137
138
  // platform, the Channels tab every platform, one under the other.
138
139
  const platforms = platform
139
140
  ? [platform]
140
- : ['slack', 'teams', ...(emailEnabled ? ['email'] : []), 'telegram'];
141
+ : CHANNEL_PLATFORM_IDS.filter((pf) => pf !== 'email' || emailEnabled);
141
142
  // The Kortix tab also holds the computer, where this deployment can pair one
142
143
  // (the machine list answers 503 where computers are off).
143
144
  const computersEnabled = useTunnelConnections({ refetchInterval: false }).isSuccess;
@@ -153,6 +154,11 @@ export function ChannelsSection({ projectId, platform }) {
153
154
  return _jsx(EmailHero, { projectId: projectId, canWrite: canWrite });
154
155
  case 'telegram':
155
156
  return _jsx(TelegramChannelRow, { projectId: projectId, canWrite: canWrite });
157
+ default: {
158
+ // A new channel id fails here until it has a hero.
159
+ const _exhaustive = pf;
160
+ return _exhaustive;
161
+ }
156
162
  }
157
163
  };
158
164
  // The Email page with the flag off: the standard flag-off screen, with its enable action.
@@ -190,14 +196,15 @@ function ChannelsButton({ count, onOpen }) {
190
196
  }
191
197
  /**
192
198
  * What follows a connected Slack before its first channel: the one-time "now
193
- * do this in Slack" nudge. It retires itself as soon as `bindings.length > 0`:
194
- * a bound channel is proof the user already invited the bot and mentioned it,
199
+ * do this in Slack" nudge. It retires itself as soon as a Slack channel is
200
+ * bound: that is proof the user already invited the bot and mentioned it,
195
201
  * and from then on the Slack row's "N channels" opens the bindings dialog.
202
+ * Another platform's row (a Telegram chat's `/model`) proves nothing about Slack.
196
203
  */
197
204
  function SlackFollowUp({ projectId }) {
198
205
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
199
206
  const bindingsQuery = useChannelBindings(projectId);
200
- const bindings = bindingsQuery.data?.bindings ?? [];
207
+ const bindings = (bindingsQuery.data?.bindings ?? []).filter((b) => b.platform === 'slack');
201
208
  return bindings.length === 0 && !bindingsQuery.isLoading ? (_jsxs(InfoBanner, { tone: "neutral", icon: AtIcon, title: tI18nComplete.raw('textd63af8edd3d6'), children: [tI18nComplete.raw('text1fd9ae1607aa'), ' ', _jsx("span", { className: "text-foreground font-medium", children: tI18nComplete.raw('text476b90bdc143') }), ' ', tI18nComplete.raw('textb81b729b4538')] })) : null;
202
209
  }
203
210
  /**
@@ -249,7 +256,7 @@ function ChannelBindingsDialog({ projectId, canWrite, platform, open, onOpenChan
249
256
  return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: selected ? (_jsx(ChannelSettingsModalContent, { projectId: projectId, binding: selected.binding, name: selected.name, scope: selected.scope, projectDefaultAgent: projectDefaultAgent, canWrite: canWrite, onBack: back })) : (_jsxs(ModalContent, { className: "lg:max-w-lg", children: [_jsxs(ModalHeader, { children: [_jsx(ModalTitle, { children: tI18nComplete.raw('text5f61b63c2c4a') }), _jsx(ModalDescription, { children: tI18nComplete.raw('text1f2550ed44dc') })] }), _jsxs(ModalBody, { className: "space-y-3 pt-4", children: [tabs.length > 1 ? (_jsx(Tabs, { value: active, onValueChange: (value) => {
250
257
  setTab(value);
251
258
  setQuery('');
252
- }, children: _jsx(TabsListCompact, { children: tabs.map((t) => (_jsxs(TabsTriggerCompact, { value: t.platform, className: "gap-1.5", children: [PLATFORM_NAMES[t.platform] ?? t.platform, _jsx("span", { className: "text-muted-foreground tabular-nums", children: t.count })] }, t.platform))) }) })) : null, inTab.length >= BINDING_SEARCH_MIN ? (_jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(MagnifyingGlassIcon, {}) }), _jsx(InputGroupSearchInput, { placeholder: tI18nComplete.raw('text49c266baaaa7'), value: query, onChange: (e) => setQuery(e.target.value), size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] })) : null, bindingsQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-12 rounded-md" }), _jsx(Skeleton, { className: "h-12 rounded-md" })] })) : visible.length === 0 ? (_jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-xs", children: tI18nComplete.raw('texte8dd87902b91') })) : (
259
+ }, children: _jsx(TabsListCompact, { children: tabs.map((t) => (_jsxs(TabsTriggerCompact, { value: t.platform, className: "gap-1.5", children: [isChannelPlatform(t.platform) ? CHANNELS[t.platform].label : t.platform, _jsx("span", { className: "text-muted-foreground tabular-nums", children: t.count })] }, t.platform))) }) })) : null, inTab.length >= BINDING_SEARCH_MIN ? (_jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(MagnifyingGlassIcon, {}) }), _jsx(InputGroupSearchInput, { placeholder: tI18nComplete.raw('text49c266baaaa7'), value: query, onChange: (e) => setQuery(e.target.value), size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] })) : null, bindingsQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-12 rounded-md" }), _jsx(Skeleton, { className: "h-12 rounded-md" })] })) : visible.length === 0 ? (_jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-xs", children: tI18nComplete.raw('texte8dd87902b91') })) : (
253
260
  // The list scrolls; the tabs and search above it stay put. Capped at
254
261
  // the viewport less ~21rem of header, tabs, search and footer, so
255
262
  // the footer stays on screen at the 720 × 480 desktop minimum,
@@ -262,8 +269,6 @@ function ChannelBindingsDialog({ projectId, canWrite, platform, open, onOpenChan
262
269
  _jsx(BindingOverrides, { binding: binding, policies: conversationPolicies }, "overrides"),
263
270
  ], onClick: () => setSelectedId(binding.bindingId) }, binding.bindingId))) }))] }), _jsx(ModalFooter, { className: "pt-2 pb-5", children: _jsx(Button, { type: "button", variant: "outline-ghost", onClick: () => onOpenChange(false), children: tI18nComplete.raw('text11a6767d5674') }) })] })) }));
264
271
  }
265
- /** Brand names, as the platform rows spell them. */
266
- const PLATFORM_NAMES = { slack: 'Slack', teams: 'Microsoft Teams' };
267
272
  /** A row's settings at a glance: only what overrides the project, else "Project default". */
268
273
  function BindingOverrides({ binding, policies, }) {
269
274
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
@@ -1,5 +1,6 @@
1
1
  import { ArrowCircleUpIcon as ArrowUpCircle, ChatCircleIcon as ChatCircle, CoinsIcon as Coins, ShippingContainerIcon as Container, CreditCardIcon as CreditCard, FlaskIcon as Flask, KeyIcon as Key, LaptopIcon as Laptop, LinkIcon as Link, PaletteIcon as Palette, ShieldCheckIcon as ShieldCheck, SlidersHorizontalIcon as SlidersHorizontal, SquaresFourIcon as SquaresFour, UserIcon as User, } from '@phosphor-icons/react';
2
2
  import { Mcp } from '../../internal/icons/mcp.js';
3
+ import { ChromeRailIcon } from '../../internal/chrome/browser-icon.js';
3
4
  /**
4
5
  * Whether a rail item is the active one for the current settings tab.
5
6
  *
@@ -64,6 +65,14 @@ const STATIC_GROUPS = [
64
65
  description: 'Language and keyboard shortcuts.',
65
66
  icon: SlidersHorizontal,
66
67
  },
68
+ {
69
+ tab: 'chrome',
70
+ label: 'Kortix in Chrome',
71
+ description: 'Your sessions use Chrome with your logins. You see every step.',
72
+ docsHref: '/docs/connect/chrome',
73
+ // Monochrome like its neighbours; `ChromeRailIcon` takes only `className`.
74
+ icon: ChromeRailIcon,
75
+ },
67
76
  // Labelled "API keys", routed at `/settings/tokens`. The id has to be
68
77
  // `tokens` — it is the URL segment, and the account page already spends
69
78
  // `api-keys` on a legacy redirect (`RENAMED` in `settings-tabs.ts`) —
@@ -1,4 +1,4 @@
1
- export type SettingsTab = 'profile' | 'security' | 'appearance' | 'sessions' | 'preferences' | 'connected' | 'tokens' | 'plan' | 'credits' | 'workspace' | 'sandbox' | 'feature-flags' | 'upgrades' | 'mcp' | 'computer';
1
+ export type SettingsTab = 'profile' | 'security' | 'appearance' | 'sessions' | 'preferences' | 'connected' | 'chrome' | 'tokens' | 'plan' | 'credits' | 'workspace' | 'sandbox' | 'feature-flags' | 'upgrades' | 'mcp' | 'computer';
2
2
  /**
3
3
  * The default must be a tab that survives every gate, or the overlay opens on
4
4
  * nothing. It was `general` until project configuration moved to
@@ -54,6 +54,7 @@ export const SETTINGS_TABS = [
54
54
  'appearance',
55
55
  'sessions',
56
56
  'preferences',
57
+ 'chrome',
57
58
  'tokens',
58
59
  'mcp',
59
60
  'computer',
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Settings → Kortix in Chrome: the browsers this person connected with the
3
+ * Kortix extension. With none, the three connect steps stand inline and the
4
+ * connect action sits in the page header.
5
+ */
6
+ export declare function ChromeConnectionsTab(): import("react").JSX.Element;
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useChromeConnections, useDeleteChromeConnection, useUpdateChromeConnection } from '@kortix/sdk/react';
4
+ import { PencilSimpleIcon } from '@phosphor-icons/react';
5
+ import { useState } from 'react';
6
+ import { Button } from '../../../primitives/button.js';
7
+ import { ConfirmDialog } from '../../../primitives/confirm-dialog.js';
8
+ import { InlineMeta } from '../../../primitives/inline-meta.js';
9
+ import { Input } from '../../../primitives/input.js';
10
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../primitives/select.js';
11
+ import { SettingsRow, SettingsRowGroup } from '../../../primitives/settings-row.js';
12
+ import { SettingsSubsectionHeader } from '../../../primitives/settings-subsection-header.js';
13
+ import { Skeleton } from '../../../primitives/skeleton.js';
14
+ import { errorToast, successToast } from '../../../primitives/toast.js';
15
+ import { ErrorState } from '../../../primitives/error-state.js';
16
+ import { SettingsTabHeader } from '../settings-tab-header.js';
17
+ import { useLocale, useTranslations } from '../../../i18n/index.js';
18
+ import { relativeTime } from '../../../internal/lib/relative-time.js';
19
+ import { ChromeButtonMark, ChromeConnectModal, ChromeConnectSteps, ChromeCopyLink, ChromeInstallActions, ChromeOnlineDot, useChromeConnect, } from '../../../internal/chrome/chrome-connect.js';
20
+ import { BrowserLogoTile } from '../../../internal/chrome/browser-icon.js';
21
+ import { chromeDisplayNames, offlineLongerThan } from '../../../internal/chrome/chrome-names.js';
22
+ import { ChromeMock } from '../../../internal/chrome/chrome-mock.js';
23
+ const ACCESS_MODES = ['always', 'ask', 'off'];
24
+ // `all` first: it is the recommended default.
25
+ const TAB_SCOPES = ['all', 'own'];
26
+ /** Offline longer than this shows when the browser was last seen. Shorter is just "Offline". */
27
+ const LAST_SEEN_AFTER_MS = 10 * 60_000;
28
+ /**
29
+ * Settings → Kortix in Chrome: the browsers this person connected with the
30
+ * Kortix extension. With none, the three connect steps stand inline and the
31
+ * connect action sits in the page header.
32
+ */
33
+ export function ChromeConnectionsTab() {
34
+ const t = useTranslations('chrome');
35
+ const connections = useChromeConnections();
36
+ const [connectOpen, setConnectOpen] = useState(false);
37
+ const list = connections.data ?? [];
38
+ if (connections.isSuccess && list.length === 0)
39
+ return _jsx(ChromeEmptyTab, {});
40
+ const names = chromeDisplayNames(list);
41
+ return (
42
+ // `max-w-2xl`: the form tier every person-scoped pane in this dialog uses.
43
+ _jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-5", children: [_jsx(SettingsTabHeader, { tab: "chrome", action: list.length > 0 ? (_jsxs(Button, { size: "sm", variant: "secondary", className: "gap-1.5", onClick: () => setConnectOpen(true), children: [_jsx(ChromeButtonMark, {}), t('connectAnother')] })) : undefined }), connections.isPending ? (_jsx("div", { className: "space-y-2", children: _jsx(Skeleton, { className: "h-24 rounded-md" }) })) : connections.isError ? (_jsx(ErrorState, { size: "sm", title: t('loadFailed'), action: _jsx(Button, { variant: "outline", size: "sm", onClick: () => connections.refetch(), children: t('retry') }) })) : (_jsxs(_Fragment, { children: [_jsxs("section", { className: "space-y-3", children: [_jsx(SettingsSubsectionHeader, { title: t('listTitle') }), _jsx("ul", { className: "space-y-3", children: list.map((chrome) => (_jsx(ChromeRow, { chrome: chrome, displayName: names.get(chrome.id) ?? chrome.name }, chrome.id))) })] }), _jsxs("section", { className: "space-y-2", children: [_jsx(SettingsSubsectionHeader, { title: t('how.title') }), _jsxs("ul", { className: "text-muted-foreground list-disc space-y-1 pl-5 text-sm text-pretty", children: [_jsx("li", { children: t('how.group') }), _jsx("li", { children: t('how.control') }), _jsx("li", { children: t('how.own') })] })] })] })), _jsx(ChromeConnectModal, { open: connectOpen, onOpenChange: setConnectOpen, fresh: true })] }));
44
+ }
45
+ /** No browser yet: the steps stand inline, the one action is in the header. */
46
+ function ChromeEmptyTab() {
47
+ const state = useChromeConnect();
48
+ return (_jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-5", children: [_jsx(SettingsTabHeader, { tab: "chrome", action: state.environment === 'unsupported' ? (_jsx(ChromeCopyLink, {})) : (_jsx(ChromeInstallActions, { state: state, showGuide: false })) }), _jsxs("div", { className: "space-y-4", children: [_jsx(ChromeMock, { className: "mx-auto max-w-md" }), _jsx("div", { className: "bg-popover flex flex-col gap-5 rounded-md border px-4 py-5", children: _jsx(ChromeConnectSteps, { state: state, showActions: false }) })] })] }));
49
+ }
50
+ /** "Chromium on macOS · Work · v1.0.0": what tells two browsers apart. The name is not repeated. */
51
+ function chromeMeta(chrome, displayName) {
52
+ // The profile label already sits in the display name of a duplicate: say it once.
53
+ const profile = chrome.profileLabel && !displayName.includes(chrome.profileLabel) ? chrome.profileLabel : null;
54
+ return [profile, chrome.extensionVersion ? `v${chrome.extensionVersion}` : null].filter((part) => Boolean(part));
55
+ }
56
+ function ChromeRow({ chrome, displayName }) {
57
+ const t = useTranslations('chrome');
58
+ const locale = useLocale();
59
+ const update = useUpdateChromeConnection();
60
+ const remove = useDeleteChromeConnection();
61
+ const [confirmOpen, setConfirmOpen] = useState(false);
62
+ const [draft, setDraft] = useState(null);
63
+ const save = (change) => update.mutate({ id: chrome.id, ...change }, { onError: () => errorToast(t('updateFailed')) });
64
+ const commitName = () => {
65
+ const name = draft?.trim();
66
+ // An empty name is not a name: keep the field open and flag it. Blur reverts.
67
+ if (!name)
68
+ return false;
69
+ setDraft(null);
70
+ if (name !== chrome.name)
71
+ save({ name });
72
+ return true;
73
+ };
74
+ const sessions = chrome.activeSessions.length;
75
+ // "Last seen just now" beside "Offline" contradicts itself: say it only once it is a while ago.
76
+ const longOffline = !chrome.online && offlineLongerThan(chrome.lastSeenAt, LAST_SEEN_AFTER_MS);
77
+ const status = chrome.online
78
+ ? t('online')
79
+ : longOffline
80
+ ? t('lastSeen', { time: relativeTime(chrome.lastSeenAt, locale) })
81
+ : t('offline');
82
+ const meta = [...chromeMeta(chrome, displayName), status];
83
+ return (_jsxs("li", { children: [_jsxs(SettingsRowGroup, { children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [_jsx(BrowserLogoTile, { brand: chrome.brand }), _jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ChromeOnlineDot, { online: chrome.online }), draft === null ? (_jsxs("button", { type: "button", "aria-label": t('rename', { name: displayName }), title: displayName, className: "focus-visible:ring-ring/50 -mx-1 flex min-w-0 items-center gap-1.5 rounded-sm px-1 text-left outline-none focus-visible:ring-3", onClick: () => setDraft(chrome.name), children: [_jsx("span", { className: "truncate text-sm font-medium", children: displayName }), _jsx(PencilSimpleIcon, { className: "text-muted-foreground size-3.5 shrink-0" })] })) : (_jsx(Input, { autoFocus: true, value: draft, maxLength: 255, "aria-label": t('renameLabel'), className: "h-6 py-0", "aria-invalid": draft.trim() === '' ? true : undefined, onChange: (event) => setDraft(event.target.value), onBlur: () => {
84
+ if (!commitName())
85
+ setDraft(null);
86
+ }, onKeyDown: (event) => {
87
+ if (event.key === 'Enter')
88
+ commitName();
89
+ // Closes the field only. The settings dialog ignores Escape from a text field.
90
+ if (event.key === 'Escape')
91
+ setDraft(null);
92
+ } }))] }), _jsxs(InlineMeta, { children: [meta, sessions > 0 ? t('usedBy', { count: sessions }) : ''] })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirmOpen(true), children: t('disconnect') })] }), _jsx(SettingsRow, { label: t('access.label'), description: t('access.description'), children: _jsxs(Select, { value: chrome.accessMode, disabled: update.isPending, onValueChange: (value) => save({ accessMode: value }), children: [_jsx(SelectTrigger, { size: "sm", className: "w-44", "aria-label": t('access.label'), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ACCESS_MODES.map((mode) => (_jsx(SelectItem, { value: mode, children: t(`access.${mode}`) }, mode))) })] }) }), _jsx(SettingsRow, { label: t('scope.label'), description: t('scope.description'), children: _jsxs(Select, { value: chrome.tabScope, disabled: update.isPending, onValueChange: (value) => save({ tabScope: value }), children: [_jsx(SelectTrigger, { size: "sm", className: "w-44", "aria-label": t('scope.label'), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: TAB_SCOPES.map((scope) => (_jsx(SelectItem, { value: scope, children: t(`scope.${scope}`) }, scope))) })] }) })] }), _jsx(ConfirmDialog, { open: confirmOpen, onOpenChange: setConfirmOpen, title: t('disconnectTitle', { name: displayName }), description: _jsxs(_Fragment, { children: [_jsx("span", { className: "text-muted-foreground mb-2 block text-xs", children: meta.join(' · ') }), t('disconnectDescription')] }), confirmLabel: t('disconnect'), confirmVariant: "destructive", isPending: remove.isPending, onConfirm: () => remove.mutate(chrome.id, {
93
+ onSuccess: () => {
94
+ setConfirmOpen(false);
95
+ successToast(t('disconnected'));
96
+ },
97
+ onError: () => errorToast(t('disconnectFailed')),
98
+ }) })] }));
99
+ }
@@ -1,8 +1,8 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /**
3
- * The "nothing here" line every catalog uses, so five hand-typed copies of one
4
- * class string cannot drift into five different-looking messages. `CatalogGrid`
5
- * owns loading/error/empty; this is what callers put IN the empty slot when the
3
+ * The "nothing here" state every catalog uses: the shared `EmptyState`, so a
4
+ * catalog's empty slot looks like every other empty view. `CatalogGrid` owns
5
+ * loading/error/empty; this is what callers put IN the empty slot when the
6
6
  * cause is the search box rather than an empty project.
7
7
  */
8
8
  export declare function CatalogEmptyNote({ children }: {
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MagnifyingGlassIcon } from '@phosphor-icons/react';
2
3
  import { useTranslations } from '../../../i18n/index.js';
4
+ import { EmptyState } from '../../../primitives/empty-state.js';
3
5
  /**
4
- * The "nothing here" line every catalog uses, so five hand-typed copies of one
5
- * class string cannot drift into five different-looking messages. `CatalogGrid`
6
- * owns loading/error/empty; this is what callers put IN the empty slot when the
6
+ * The "nothing here" state every catalog uses: the shared `EmptyState`, so a
7
+ * catalog's empty slot looks like every other empty view. `CatalogGrid` owns
8
+ * loading/error/empty; this is what callers put IN the empty slot when the
7
9
  * cause is the search box rather than an empty project.
8
10
  */
9
11
  export function CatalogEmptyNote({ children }) {
10
- return _jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-xs", children: children });
12
+ return _jsx(EmptyState, { size: "sm", icon: MagnifyingGlassIcon, title: children });
11
13
  }
12
14
  /**
13
15
  * "No matches for `<query>`."
@@ -27,6 +27,8 @@ export interface ImageFrameProps {
27
27
  naturalRatio?: string | null;
28
28
  /** Reports the image's natural size once it decoded. */
29
29
  onNaturalSize?: (width: number, height: number) => void;
30
+ /** Drawn over the frame's own box (an overlay on the image's corner), which narrows with `maxHeight`. */
31
+ children?: ReactNode;
30
32
  }
31
33
  /**
32
34
  * The box an image takes before, while and after it exists: a flat `bg-muted`
@@ -35,7 +37,7 @@ export interface ImageFrameProps {
35
37
  * The frame itself never moves: the busy signal is the label (one waiting
36
38
  * channel, kortix-brand `layout.md` States).
37
39
  */
38
- export declare function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill, maxHeight, openLabel, naturalRatio, onNaturalSize, }: ImageFrameProps): import("react").JSX.Element;
40
+ export declare function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill, maxHeight, openLabel, naturalRatio, onNaturalSize, children, }: ImageFrameProps): import("react").JSX.Element;
39
41
  /**
40
42
  * An image whose file could not be read: "Image unavailable" and Try again,
41
43
  * centered in the frame. Never the read's own error text (R38): that is
@@ -16,7 +16,7 @@ function ratioValue(ratio) {
16
16
  * The frame itself never moves: the busy signal is the label (one waiting
17
17
  * channel, kortix-brand `layout.md` States).
18
18
  */
19
- export function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill = false, maxHeight, openLabel, naturalRatio = null, onNaturalSize, }) {
19
+ export function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill = false, maxHeight, openLabel, naturalRatio = null, onNaturalSize, children, }) {
20
20
  const img = useRef(null);
21
21
  const [loaded, setLoaded] = useState(false);
22
22
  const [natural, setNatural] = useState(naturalRatio);
@@ -46,7 +46,7 @@ export function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, clas
46
46
  : {
47
47
  aspectRatio: natural ?? aspectRatio,
48
48
  ...(maxHeight && { width: `min(100%, calc(${maxHeight} * ${ratioValue(natural ?? aspectRatio)}))` }),
49
- }, children: [image && onOpen ? (_jsx("button", { type: "button", onClick: onOpen, "aria-label": openLabel ?? alt, className: "focus-visible:ring-kortix-base block size-full cursor-zoom-in rounded-md focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset", children: image })) : (image), label ? (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4 text-center", children: label })) : null] }));
49
+ }, children: [image && onOpen ? (_jsx("button", { type: "button", onClick: onOpen, "aria-label": openLabel ?? alt, className: "focus-visible:ring-kortix-base block size-full cursor-zoom-in rounded-md focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset", children: image })) : (image), label ? (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4 text-center", children: label })) : null, children] }));
50
50
  }
51
51
  /**
52
52
  * An image whose file could not be read: "Image unavailable" and Try again,
package/dist/i18n/en.d.ts CHANGED
@@ -371,6 +371,10 @@ declare const catalog: {
371
371
  connected: {
372
372
  label: string;
373
373
  };
374
+ chrome: {
375
+ label: string;
376
+ description: string;
377
+ };
374
378
  tokens: {
375
379
  label: string;
376
380
  description: string;
@@ -835,6 +839,14 @@ declare const catalog: {
835
839
  name: string;
836
840
  description: string;
837
841
  };
842
+ artifact_comments: {
843
+ name: string;
844
+ description: string;
845
+ };
846
+ chrome: {
847
+ name: string;
848
+ description: string;
849
+ };
838
850
  };
839
851
  channelsSwitch: {
840
852
  onTitle: string;
@@ -1161,6 +1173,7 @@ declare const catalog: {
1161
1173
  loadError: string;
1162
1174
  createIn: string;
1163
1175
  localComputer: string;
1176
+ appsAndExtensions: string;
1164
1177
  };
1165
1178
  manifestUpgrade: {
1166
1179
  title: string;
@@ -1772,6 +1785,13 @@ declare const catalog: {
1772
1785
  unexpected: string;
1773
1786
  };
1774
1787
  };
1788
+ panel: {
1789
+ waitingTitle: string;
1790
+ waitingBody: string;
1791
+ errorTitle: string;
1792
+ errorBody: string;
1793
+ signIn: string;
1794
+ };
1775
1795
  };
1776
1796
  tools: {
1777
1797
  executeCommand: string;
@@ -6777,6 +6797,127 @@ declare const catalog: {
6777
6797
  calendarEstimatedSr: string;
6778
6798
  calendarUpcomingSr: string;
6779
6799
  };
6800
+ chrome: {
6801
+ pill: string;
6802
+ connectTitle: string;
6803
+ connectDescription: string;
6804
+ promoLabel: string;
6805
+ dismissPromo: string;
6806
+ steps: {
6807
+ install: {
6808
+ title: string;
6809
+ description: string;
6810
+ done: string;
6811
+ descriptionUnlisted: string;
6812
+ };
6813
+ connect: {
6814
+ title: string;
6815
+ description: string;
6816
+ done: string;
6817
+ };
6818
+ done: {
6819
+ title: string;
6820
+ description: string;
6821
+ };
6822
+ };
6823
+ addToChrome: string;
6824
+ finishInChrome: string;
6825
+ connect: string;
6826
+ connecting: string;
6827
+ done: string;
6828
+ unsupported: string;
6829
+ connectFailed: string;
6830
+ online: string;
6831
+ offline: string;
6832
+ lastSeen: string;
6833
+ usedBy: string;
6834
+ connectAnother: string;
6835
+ listTitle: string;
6836
+ rename: string;
6837
+ renameLabel: string;
6838
+ disconnect: string;
6839
+ disconnectTitle: string;
6840
+ disconnectDescription: string;
6841
+ disconnected: string;
6842
+ disconnectFailed: string;
6843
+ updateFailed: string;
6844
+ loadFailed: string;
6845
+ retry: string;
6846
+ access: {
6847
+ label: string;
6848
+ always: string;
6849
+ ask: string;
6850
+ off: string;
6851
+ description: string;
6852
+ };
6853
+ scope: {
6854
+ label: string;
6855
+ own: string;
6856
+ all: string;
6857
+ description: string;
6858
+ };
6859
+ how: {
6860
+ title: string;
6861
+ group: string;
6862
+ control: string;
6863
+ own: string;
6864
+ };
6865
+ howToInstall: string;
6866
+ copyLink: string;
6867
+ linkCopied: string;
6868
+ copyFailed: string;
6869
+ extensionMissing: string;
6870
+ another: {
6871
+ title: string;
6872
+ waiting: string;
6873
+ description: string;
6874
+ };
6875
+ panel: {
6876
+ project: string;
6877
+ title: string;
6878
+ body: string;
6879
+ selectionTitle: string;
6880
+ selectionBody: string;
6881
+ actions: {
6882
+ summarize: string;
6883
+ tables: string;
6884
+ compare: string;
6885
+ email: string;
6886
+ };
6887
+ prompts: {
6888
+ summarize: string;
6889
+ tables: string;
6890
+ compare: string;
6891
+ email: string;
6892
+ };
6893
+ recent: string;
6894
+ noProjects: string;
6895
+ createProject: string;
6896
+ addTab: string;
6897
+ addTabsTitle: string;
6898
+ addTabsHint: string;
6899
+ addTabs: string;
6900
+ noTabs: string;
6901
+ tabsFailed: string;
6902
+ cancel: string;
6903
+ removeTab: string;
6904
+ removeQuote: string;
6905
+ wholePage: string;
6906
+ unreadable: string;
6907
+ message: string;
6908
+ placeholder: string;
6909
+ placeholderNoTab: string;
6910
+ modeLabel: string;
6911
+ read: string;
6912
+ act: string;
6913
+ readHint: string;
6914
+ actHint: string;
6915
+ send: string;
6916
+ readFailed: string;
6917
+ screenshot: string;
6918
+ captureFailed: string;
6919
+ };
6920
+ };
6780
6921
  computers: {
6781
6922
  connectYourComputer: string;
6782
6923
  connectThisComputer: string;
@@ -8087,5 +8228,53 @@ declare const catalog: {
8087
8228
  tryAgain: string;
8088
8229
  details: string;
8089
8230
  };
8231
+ appsModal: {
8232
+ title: string;
8233
+ subtitle: string;
8234
+ mock: {
8235
+ group: string;
8236
+ button: string;
8237
+ working: string;
8238
+ takeOver: string;
8239
+ stop: string;
8240
+ };
8241
+ chrome: {
8242
+ name: string;
8243
+ description: string;
8244
+ connect: string;
8245
+ connecting: string;
8246
+ manage: string;
8247
+ unsupported: string;
8248
+ group: string;
8249
+ manageLabel: string;
8250
+ summary: string;
8251
+ };
8252
+ computer: {
8253
+ title: string;
8254
+ thisComputer: string;
8255
+ thisComputerDescription: string;
8256
+ connected: string;
8257
+ connect: string;
8258
+ manage: string;
8259
+ desktop: string;
8260
+ desktopDescription: string;
8261
+ desktopRunning: string;
8262
+ download: string;
8263
+ manageLabel: string;
8264
+ connectLabel: string;
8265
+ };
8266
+ developers: {
8267
+ title: string;
8268
+ cli: string;
8269
+ cliDescription: string;
8270
+ copy: string;
8271
+ copied: string;
8272
+ mcp: string;
8273
+ mcpDescription: string;
8274
+ connect: string;
8275
+ connectLabel: string;
8276
+ copyFailed: string;
8277
+ };
8278
+ };
8090
8279
  };
8091
8280
  export default catalog;