@kortix/ui 0.14.11 → 0.14.12

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.
@@ -2,6 +2,7 @@ interface PreviewTabContentProps {
2
2
  tabId: string;
3
3
  projectId?: string;
4
4
  projectSessionId?: string;
5
+ targetUrl?: string;
5
6
  }
6
7
  /**
7
8
  * Preview tab content — renders a proxied sandbox URL in an iframe
@@ -11,5 +12,5 @@ interface PreviewTabContentProps {
11
12
  * any localhost:PORT address to navigate within the sandbox. Visited URLs are
12
13
  * recorded and offered back as "Recents" on the empty landing state.
13
14
  */
14
- export declare function BrowserPanel({ tabId, projectId, projectSessionId }: PreviewTabContentProps): import("react").JSX.Element;
15
+ export declare function BrowserPanel({ tabId, projectId, projectSessionId, targetUrl }: PreviewTabContentProps): import("react").JSX.Element;
15
16
  export {};
@@ -43,7 +43,7 @@ const subscribeNoop = () => () => { };
43
43
  * any localhost:PORT address to navigate within the sandbox. Visited URLs are
44
44
  * recorded and offered back as "Recents" on the empty landing state.
45
45
  */
46
- export function BrowserPanel({ tabId, projectId, projectSessionId }) {
46
+ export function BrowserPanel({ tabId, projectId, projectSessionId, targetUrl }) {
47
47
  const tHardcodedUi = useTranslations('hardcodedUi');
48
48
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
49
49
  const appPreviewTitle = tI18nComplete.raw('text6026c1d8c92e');
@@ -69,7 +69,7 @@ export function BrowserPanel({ tabId, projectId, projectSessionId }) {
69
69
  // reveal scroll shoves a permanent sideways offset onto the panel's
70
70
  // overflow-hidden ancestors. Mount-only on purpose (`autoFocus` semantics —
71
71
  // a later navigation clearing the URL must not steal the cursor).
72
- const autoFocusAddressRef = useRef(!rawPreviewUrl);
72
+ const autoFocusAddressRef = useRef(!rawPreviewUrl && !targetUrl);
73
73
  useEffect(() => {
74
74
  if (autoFocusAddressRef.current)
75
75
  focusWithoutScroll(addressInputRef.current);
@@ -200,6 +200,16 @@ export function BrowserPanel({ tabId, projectId, projectSessionId }) {
200
200
  pushHistory(newProxyUrl);
201
201
  handleRefresh();
202
202
  }, [subdomainOpts, rewritePortPath, tabId, appPreviewTitle, updateTabMetadata, pushHistory, handleRefresh]);
203
+ const appliedTarget = useRef(null);
204
+ useEffect(() => {
205
+ if (!targetUrl || (appliedTarget.current?.tabId === tabId && appliedTarget.current.url === targetUrl))
206
+ return;
207
+ appliedTarget.current = { tabId, url: targetUrl };
208
+ // An external quick-view intent synchronizes the Browser tab and its navigation history.
209
+ // eslint-disable-next-line react-hooks/set-state-in-effect
210
+ if (targetUrl !== rawPreviewUrl && targetUrl !== originalUrl)
211
+ navigateTo(targetUrl);
212
+ }, [tabId, targetUrl, rawPreviewUrl, originalUrl, navigateTo]);
203
213
  const canGoBack = historyIndex > 0;
204
214
  const canGoForward = historyIndex < history.length - 1;
205
215
  /** Walk the history stack in either direction; the two directions were two
package/dist/i18n/en.d.ts CHANGED
@@ -4878,6 +4878,7 @@ declare const catalog: {
4878
4878
  textce1e609b7bf5: string;
4879
4879
  textce34af36d804: string;
4880
4880
  textce4683e7013a: string;
4881
+ text68971283841a: string;
4881
4882
  textce49e021df99: string;
4882
4883
  textce4c96225f63: string;
4883
4884
  textce5e9df4a78f: string;
package/dist/i18n/en.js CHANGED
@@ -4879,6 +4879,7 @@ const catalog = {
4879
4879
  "textce1e609b7bf5": "Sandbox ports only",
4880
4880
  "textce34af36d804": "Kortix CompanyOS – the open-source operating system for your company",
4881
4881
  "textce4683e7013a": "Channel",
4882
+ "text68971283841a": "Subject",
4882
4883
  "textce49e021df99": "The session owner or a project manager can reload it.",
4883
4884
  "textce4c96225f63": "1 attempt",
4884
4885
  "textce5e9df4a78f": "Service account",
@@ -24,6 +24,9 @@ const EXTENSION_PROTOCOL_PREFIXES = [
24
24
  ];
25
25
  const INJECTED_APP_SOURCE_PATTERNS = [
26
26
  /^app:\/\/\/scripts\/inpage\.js$/,
27
+ // Injected extension content script; KRTX-3704's onMessage TypeError has
28
+ // only this source, never a first-party _next bundle or resolved source.
29
+ /^app:\/\/\/js\/content\.js$/,
27
30
  // CookieYes (https://www.cookieyes.com) cookie-consent SDK runtime, served
28
31
  // from its own CDN workspace directory
29
32
  // `https://cdn-cookieyes.com/client_data/<workspace-id>/…`: `script.js` is
@@ -100,6 +100,7 @@ interface KortixComputerState {
100
100
  target?: QuickViewTarget;
101
101
  } | null;
102
102
  mobileToolView: QuickView | null;
103
+ mobileToolTarget: QuickViewTarget | null;
103
104
  openFileInComputer: (filePath: string, filePathList?: string[], targetLine?: number) => void;
104
105
  openFileBrowser: () => void;
105
106
  navigateToToolCall: (toolIndex: number) => void;
@@ -196,7 +197,7 @@ interface KortixComputerState {
196
197
  target?: QuickViewTarget;
197
198
  } | null;
198
199
  /** Mobile only — open `view` in the standalone tool drawer. */
199
- openMobileTool: (view: QuickView) => void;
200
+ openMobileTool: (view: QuickView, target?: QuickViewTarget) => void;
200
201
  closeMobileTool: () => void;
201
202
  reset: () => void;
202
203
  }
@@ -29,6 +29,7 @@ const initialState = {
29
29
  pendingPrimaryOpenSessionId: null,
30
30
  pendingQuickView: null,
31
31
  mobileToolView: null,
32
+ mobileToolTarget: null,
32
33
  /** A quick-view consumed by the transient BOOT shell (e.g. Terminal clicked
33
34
  * while the sandbox boots). The shell's provider dies at the boot→ready
34
35
  * handoff, taking the view with it — this remembers the intent so
@@ -283,6 +284,7 @@ export const useKortixComputerStore = create()(devtools((set, get) => ({
283
284
  pendingToolNavIndex: null,
284
285
  // A tool drawer belonging to the session the user just left.
285
286
  mobileToolView: null,
287
+ mobileToolTarget: null,
286
288
  // The ⌘I restore memory dies with the session. Within a session the
287
289
  // key is a minimise/restore pair; across one it is not, and a detail
288
290
  // last seen on a page the user has navigated away from must never be
@@ -421,11 +423,11 @@ export const useKortixComputerStore = create()(devtools((set, get) => ({
421
423
  }
422
424
  return { view: pending.view, target: pending.target };
423
425
  },
424
- openMobileTool: (view) => {
425
- set({ mobileToolView: view });
426
+ openMobileTool: (view, target) => {
427
+ set({ mobileToolView: view, mobileToolTarget: target ? { ...target } : null });
426
428
  },
427
429
  closeMobileTool: () => {
428
- set({ mobileToolView: null });
430
+ set({ mobileToolView: null, mobileToolTarget: null });
429
431
  },
430
432
  reset: () => {
431
433
  console.log('[KortixComputerStore] Full reset');
@@ -15,6 +15,7 @@ export { type DraftScope } from './draft/composer-draft.js';
15
15
  export { ComposerEditor, type ComposerEditorHandle } from './editor/composer-editor.js';
16
16
  export { baseExtensions } from './editor/extensions.js';
17
17
  export { MentionNode } from './editor/mention-node.js';
18
+ export { MentionChip } from '../thread/mention-chip.js';
18
19
  export { baseSuggestion, type MenuController } from './editor/suggestion.js';
19
20
  export { ReasoningEffortSelector } from './reasoning-effort-selector.js';
20
21
  export { SessionChatInput, type SessionChatInputProps } from './session-chat-input.js';
@@ -14,6 +14,7 @@ export { ComposerUnderbar } from './composer-underbar.js';
14
14
  export { ComposerEditor } from './editor/composer-editor.js';
15
15
  export { baseExtensions } from './editor/extensions.js';
16
16
  export { MentionNode } from './editor/mention-node.js';
17
+ export { MentionChip } from '../thread/mention-chip.js';
17
18
  export { baseSuggestion } from './editor/suggestion.js';
18
19
  export { ReasoningEffortSelector } from './reasoning-effort-selector.js';
19
20
  export { SessionChatInput } from './session-chat-input.js';
@@ -22,6 +22,7 @@ import { ButtonGroup } from '../../../primitives/button-group.js';
22
22
  import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '../../../primitives/drawer.js';
23
23
  import { isPlanWriteTool } from '../../turn/plan-anchor.js';
24
24
  import { cn } from '../../../lib/utils.js';
25
+ import { hasOpenFloatingLayer } from '../../../lib/z-stack.js';
25
26
  import { focusWithoutScroll } from '../../../internal/lib/utils/focus-without-scroll.js';
26
27
  import { CaretLeftIcon as ChevronLeft, CaretRightIcon as ChevronRight, } from '@phosphor-icons/react';
27
28
  import { AnimatePresence, m, useReducedMotion, } from 'motion/react';
@@ -116,6 +117,12 @@ function useDetailKeyboard(detail, isMobile, onBack, detailRef) {
116
117
  if (isTypingTarget(event.target))
117
118
  return;
118
119
  if (event.key === 'Escape') {
120
+ if (event.defaultPrevented || hasOpenFloatingLayer())
121
+ return;
122
+ const dialog = detailRef.current;
123
+ const coveringDialog = Array.from(document.querySelectorAll('[role="dialog"][data-state="open"],[role="alertdialog"][data-state="open"]')).some((layer) => !dialog || !layer.contains(dialog));
124
+ if (coveringDialog)
125
+ return;
119
126
  // Mark the press consumed: the page-wide triple-ESC-to-stop
120
127
  // (`session-chat.tsx`) skips a `defaultPrevented` Escape. Without it,
121
128
  // closing three full-screen images in a row stopped a running turn.
@@ -19,6 +19,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  */
20
20
  import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '../../primitives/drawer.js';
21
21
  import { BrowserPanel } from '../../browser/browser-panel.js';
22
+ import { ArtifactPanelPreview } from '../../artifacts/artifact-session.js';
23
+ import { parseArtifactUrl } from '../../artifacts/frame-policy.js';
22
24
  import { CloseButton } from './easy/detail-view.js';
23
25
  import { SessionAuditPanel } from './session-audit-panel.js';
24
26
  import { SessionRemindersPanel } from './session-reminders-panel.js';
@@ -41,12 +43,14 @@ const TOOL_META = {
41
43
  };
42
44
  export function MobileToolDrawer({ sessionId, projectId, projectSessionId, }) {
43
45
  const view = useKortixComputerStore((s) => s.mobileToolView);
46
+ const target = useKortixComputerStore((s) => s.mobileToolTarget);
44
47
  const closeMobileTool = useKortixComputerStore((s) => s.closeMobileTool);
45
48
  const toolMeta = useLocalizedUiCatalog(TOOL_META);
46
49
  const meta = view ? toolMeta[view] : null;
50
+ const artifactUrl = view === 'browser' && target?.url && parseArtifactUrl(target.url) ? target.url : null;
47
51
  // Terminal, Browser and Files need the computer: while it boots, the drawer
48
52
  // shows the wake status in their place.
49
- const booting = !!useOptionalSessionPanel()?.booting &&
53
+ const booting = !!useOptionalSessionPanel()?.booting && !artifactUrl &&
50
54
  (view === 'terminal' || view === 'browser' || view === 'files' || view === 'desktop');
51
- return (_jsx(Drawer, { open: view !== null, onOpenChange: (next) => !next && closeMobileTool(), children: _jsxs(DrawerContent, { className: "flex h-[95dvh] max-h-[95dvh] min-h-[95dvh] flex-col overflow-hidden p-0", children: [_jsx(DrawerHeader, { className: "shrink-0 px-4 py-3 text-left", children: _jsxs(DrawerTitle, { className: "flex items-center justify-between gap-2 text-base", children: [_jsxs("span", { className: "flex items-center gap-2.5", children: [meta && _jsx(meta.Icon, { className: "text-muted-foreground size-4 shrink-0" }), meta?.label] }), _jsx(CloseButton, { onClose: closeMobileTool, variant: "ghost" })] }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [booting && _jsx(SessionBootStatus, { projectId: projectId, sessionId: projectSessionId }), !booting && view === 'terminal' && (_jsx(SessionTerminalPanel, { sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'browser' && (_jsx(BrowserPanel, { tabId: sessionPreviewTabId(sessionId), projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'files' && (_jsx(SessionFilesExplorer, { chatSessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'desktop' && (_jsx(SessionDesktop, { projectId: projectId, projectSessionId: projectSessionId, onClose: closeMobileTool })), view === 'audit' && (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })), view === 'reminders' && (_jsx("div", { className: "h-full overflow-y-auto p-4", children: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }) }))] })] }) }));
55
+ return (_jsx(Drawer, { open: view !== null, onOpenChange: (next) => !next && closeMobileTool(), children: _jsxs(DrawerContent, { className: "flex h-[95dvh] max-h-[95dvh] min-h-[95dvh] flex-col overflow-hidden p-0", children: [_jsx(DrawerHeader, { className: "shrink-0 px-4 py-3 text-left", children: _jsxs(DrawerTitle, { className: "flex items-center justify-between gap-2 text-base", children: [_jsxs("span", { className: "flex items-center gap-2.5", children: [meta && _jsx(meta.Icon, { className: "text-muted-foreground size-4 shrink-0" }), meta?.label] }), _jsx(CloseButton, { onClose: closeMobileTool, variant: "ghost" })] }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [booting && _jsx(SessionBootStatus, { projectId: projectId, sessionId: projectSessionId }), !booting && view === 'terminal' && (_jsx(SessionTerminalPanel, { sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), artifactUrl && _jsx(ArtifactPanelPreview, { url: artifactUrl, onClose: closeMobileTool }, artifactUrl), !booting && view === 'browser' && !artifactUrl && (_jsx(BrowserPanel, { tabId: sessionPreviewTabId(sessionId), projectId: projectId, projectSessionId: projectSessionId, targetUrl: target?.url })), !booting && view === 'files' && (_jsx(SessionFilesExplorer, { chatSessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'desktop' && (_jsx(SessionDesktop, { projectId: projectId, projectSessionId: projectSessionId, onClose: closeMobileTool })), view === 'audit' && (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })), view === 'reminders' && (_jsx("div", { className: "h-full overflow-y-auto p-4", children: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }) }))] })] }) }));
52
56
  }
@@ -43,7 +43,7 @@ target) {
43
43
  // for behind the close button. The panel toggle still opens the panel;
44
44
  // this path is only the "show me this one tool" intent.
45
45
  if (isMobileViewport()) {
46
- useKortixComputerStore.getState().openMobileTool(view);
46
+ useKortixComputerStore.getState().openMobileTool(view, target);
47
47
  track('panel_opened', { source, surface: 'mobile-tool', view });
48
48
  return;
49
49
  }
@@ -18,10 +18,11 @@ export declare const BUBBLE_TEXT: string;
18
18
  export declare const BUBBLE_SURFACE: string;
19
19
  /**
20
20
  * Where a channel message came from, in the pill's hover card: the platform,
21
- * the channel or chat it was posted in, and who posted it. A Slack prompt
22
- * written before channel names were recorded carries a bare id (`C0DEV`); the
23
- * project's Slack bindings name it, read only while this card is open. A Teams
24
- * conversation id names nothing a person reads, so Teams shows no channel row.
21
+ * the channel or chat it was posted in (an email's subject, labelled Subject),
22
+ * and who posted it. A Slack prompt written before channel names were recorded
23
+ * carries a bare id (`C0DEV`); the project's Slack bindings name it, read only
24
+ * while this card is open. A Teams conversation id names nothing a person
25
+ * reads, so Teams shows no channel row.
25
26
  */
26
27
  export declare function ChannelOrigin({ info, platform }: {
27
28
  info: ChannelMessageInfo;
@@ -78,10 +78,11 @@ export const BUBBLE_TEXT = cn('text-[0.9rem] leading-[22px] font-medium', 'wrap-
78
78
  export const BUBBLE_SURFACE = cn('bg-muted text-foreground flex max-w-full flex-col px-3.5 py-2.5 select-none rounded-lg');
79
79
  /**
80
80
  * Where a channel message came from, in the pill's hover card: the platform,
81
- * the channel or chat it was posted in, and who posted it. A Slack prompt
82
- * written before channel names were recorded carries a bare id (`C0DEV`); the
83
- * project's Slack bindings name it, read only while this card is open. A Teams
84
- * conversation id names nothing a person reads, so Teams shows no channel row.
81
+ * the channel or chat it was posted in (an email's subject, labelled Subject),
82
+ * and who posted it. A Slack prompt written before channel names were recorded
83
+ * carries a bare id (`C0DEV`); the project's Slack bindings name it, read only
84
+ * while this card is open. A Teams conversation id names nothing a person
85
+ * reads, so Teams shows no channel row.
85
86
  */
86
87
  export function ChannelOrigin({ info, platform }) {
87
88
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
@@ -92,8 +93,9 @@ export function ChannelOrigin({ info, platform }) {
92
93
  ? data?.bindings.find((b) => b.platform === 'slack' && b.channelId === info.context)
93
94
  : undefined;
94
95
  const channel = info.platform === 'Teams' ? '' : (binding && slackConversationName(binding)) || info.context;
96
+ const place = info.platform === 'Email' ? tI18nComplete('text68971283841a') : tI18nComplete('textce4683e7013a');
95
97
  return (_jsx(SourceCard, { mark: _jsx(ChannelBrandMark, { platform: info.platform, className: "size-3.5 shrink-0" }), title: platform, rows: [
96
- { label: tI18nComplete('textce4683e7013a'), value: channel },
98
+ { label: place, value: channel },
97
99
  { label: tI18nComplete('text218197693424'), value: info.userName },
98
100
  ] }));
99
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortix/ui",
3
- "version": "0.14.11",
3
+ "version": "0.14.12",
4
4
  "description": "Kortix UI: the shared React component library for apps/web, Kortix Apps and SDK hosts. Tailwind v4 classes, themed by CSS variables.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -2193,7 +2193,7 @@
2193
2193
  "@extend-ai/react-docx": "^0.7.5",
2194
2194
  "@extend-ai/react-xlsx": "0.13.4",
2195
2195
  "@glideapps/glide-data-grid": "6.0.4-alpha24",
2196
- "@kortix/llm-catalog": "0.14.11",
2196
+ "@kortix/llm-catalog": "0.14.12",
2197
2197
  "@lezer/highlight": "1.2.3",
2198
2198
  "@paper-design/shaders-react": "^0.0.76",
2199
2199
  "@phosphor-icons/react": "2.1.10",
@@ -2285,7 +2285,7 @@
2285
2285
  "zustand": "^5.0.3"
2286
2286
  },
2287
2287
  "peerDependencies": {
2288
- "@kortix/sdk": "^0.14.11",
2288
+ "@kortix/sdk": "^0.14.12",
2289
2289
  "@tanstack/react-query": "^5.75.2",
2290
2290
  "@types/react": ">=18",
2291
2291
  "react": ">=19",
@@ -305,7 +305,7 @@ export function Ask({ projectId, onAsk }: { projectId: string; onAsk: (text: str
305
305
  }
306
306
  ```
307
307
 
308
- Names: `ComposerAboveCard`, `ComposerCard`, `AgentSelector`, `agentDisplayLabel`, `captureAttachmentSubmission`, `runComposerSend`, `AttachmentSubmission`, `COMMAND_CHIP_ATTRIBUTE`, `COMMAND_CHIP_LABEL_ATTRIBUTE`, `COMMAND_CHIP_SELECTOR`, `COMMAND_CHIP_VALUE`, `draftWillRunCommand`, `planCommandAttachments`, `planUnresolvedCommandChip`, `readCommandChipLabel`, `COMPOSER_INPUT_SLOT_CLASS`, `COMPOSER_SHELL_CLASS`, `Composer`, `ComposerChatInput`, `ComposerOptions`, `ComposerDropZone`, `ComposerUnderbar`, `DraftScope`, `ComposerEditor`, `ComposerEditorHandle`, `baseExtensions`, `MentionNode`, `baseSuggestion`, `MenuController`, `ReasoningEffortSelector`, `SessionChatInput`, `SessionChatInputProps`, `useFirstPromptPreviewStore`, `useSessionComposerPrefillStore`, `createSubmitLatch`, `AttachedFile`
308
+ Names: `ComposerAboveCard`, `ComposerCard`, `AgentSelector`, `agentDisplayLabel`, `captureAttachmentSubmission`, `runComposerSend`, `AttachmentSubmission`, `COMMAND_CHIP_ATTRIBUTE`, `COMMAND_CHIP_LABEL_ATTRIBUTE`, `COMMAND_CHIP_SELECTOR`, `COMMAND_CHIP_VALUE`, `draftWillRunCommand`, `planCommandAttachments`, `planUnresolvedCommandChip`, `readCommandChipLabel`, `COMPOSER_INPUT_SLOT_CLASS`, `COMPOSER_SHELL_CLASS`, `Composer`, `ComposerChatInput`, `ComposerOptions`, `ComposerDropZone`, `ComposerUnderbar`, `DraftScope`, `ComposerEditor`, `ComposerEditorHandle`, `baseExtensions`, `MentionChip`, `MentionNode`, `baseSuggestion`, `MenuController`, `ReasoningEffortSelector`, `SessionChatInput`, `SessionChatInputProps`, `useFirstPromptPreviewStore`, `useSessionComposerPrefillStore`, `createSubmitLatch`, `AttachedFile`
309
309
 
310
310
  ## `@kortix/ui/session`
311
311