@kortix/ui 0.14.5 → 0.14.6

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 (124) hide show
  1. package/dist/access/access/access-dialog.js +8 -5
  2. package/dist/access/access/copy-row.js +2 -2
  3. package/dist/access/sharing-picker.js +2 -1
  4. package/dist/browser/app-in-browser.js +5 -3
  5. package/dist/browser/browser-panel.js +6 -6
  6. package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
  7. package/dist/browser/sandbox-browser-chrome.js +4 -3
  8. package/dist/customize/agents/agent-access.d.ts +20 -0
  9. package/dist/customize/agents/agent-access.js +17 -0
  10. package/dist/customize/agents/agent-grant-pages.js +5 -6
  11. package/dist/customize/agents/agent-page.js +2 -1
  12. package/dist/customize/agents/agents-page.js +18 -1
  13. package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
  14. package/dist/customize/connectors/connector-routes.d.ts +3 -1
  15. package/dist/customize/connectors/connector-routes.js +18 -0
  16. package/dist/customize/connectors/connectors-page.js +23 -4
  17. package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
  18. package/dist/customize/providers/provider-accordion.js +2 -1
  19. package/dist/customize/providers/provider-connect.js +2 -1
  20. package/dist/customize/sections/component/telegram-channel-row.js +1 -1
  21. package/dist/customize/sections/connector-connection-modal.js +11 -9
  22. package/dist/customize/sections/connectors-view.js +11 -13
  23. package/dist/customize/sections/view/gateway/gateway-routing.js +15 -18
  24. package/dist/customize/settings/rail-copy-context.js +5 -3
  25. package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
  26. package/dist/customize/shared/capability-tabs.js +2 -1
  27. package/dist/customize/skills/skills-page.js +3 -5
  28. package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
  29. package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
  30. package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
  31. package/dist/customize/triggers/schedule/when-event.js +1 -1
  32. package/dist/diff/diff-view.js +9 -10
  33. package/dist/files/editor/code-editor.js +2 -3
  34. package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
  35. package/dist/files/project/components/change-request-detail-dialog.js +3 -5
  36. package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
  37. package/dist/files/project/components/change-request-status-badge.js +27 -0
  38. package/dist/files/project/components/change-requests-panel.js +1 -1
  39. package/dist/files/project/components/drive-explorer.js +2 -1
  40. package/dist/files/project/components/drive-grid-view.js +11 -7
  41. package/dist/files/project/components/drive-list-view.js +4 -4
  42. package/dist/files/project/components/file-breadcrumbs.js +1 -1
  43. package/dist/files/project/components/file-explorer-page.js +1 -1
  44. package/dist/files/project/components/file-history-popover.js +1 -6
  45. package/dist/files/project/components/file-search.js +6 -1
  46. package/dist/files/project/components/open-change-request-dialog.js +2 -1
  47. package/dist/files/project/components/version-selector-logic.d.ts +11 -0
  48. package/dist/files/project/components/version-selector-logic.js +26 -0
  49. package/dist/files/project/components/version-selector.js +11 -20
  50. package/dist/files/project/hooks/use-server-health.js +17 -15
  51. package/dist/files/renderers/docx/docx-viewer.js +0 -5
  52. package/dist/files/renderers/image-renderer.js +6 -24
  53. package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
  54. package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
  55. package/dist/files/renderers/show-content-renderer.js +1 -1
  56. package/dist/files/renderers/sqlite-renderer.js +8 -3
  57. package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
  58. package/dist/files/tree/components/file-tree-item.js +1 -1
  59. package/dist/files/viewer/file-preview-modal.js +1 -1
  60. package/dist/files/viewer/use-file-content-state.js +1 -1
  61. package/dist/i18n/en.d.ts +10 -0
  62. package/dist/i18n/en.js +10 -0
  63. package/dist/internal/desktop/desktop-back-button.js +5 -3
  64. package/dist/internal/hooks/use-auto-scroll.js +5 -3
  65. package/dist/internal/notifications/notification-rows.js +2 -1
  66. package/dist/internal/outcomes/outcome-types.d.ts +5 -0
  67. package/dist/markdown/code/inline-code.js +8 -0
  68. package/dist/markdown/code/output-paths-context.d.ts +18 -0
  69. package/dist/markdown/code/output-paths-context.js +35 -0
  70. package/dist/markdown/genui/components/charts.js +6 -5
  71. package/dist/primitives/map.js +2 -1
  72. package/dist/primitives/preview-image.js +4 -2
  73. package/dist/primitives/textarea.js +1 -1
  74. package/dist/primitives/typed-title.js +12 -14
  75. package/dist/primitives/typing-animation.js +4 -2
  76. package/dist/session/composer/agent-selector.js +6 -1
  77. package/dist/session/composer/composer-chat-input.js +5 -3
  78. package/dist/session/composer/composer-logic.js +8 -7
  79. package/dist/session/composer/draft/composer-draft.js +4 -4
  80. package/dist/session/composer/pasted-text.js +3 -2
  81. package/dist/session/composer/stores/queued-draft-store.js +3 -2
  82. package/dist/session/header/reminder-parts.d.ts +11 -2
  83. package/dist/session/header/reminder-parts.js +9 -4
  84. package/dist/session/media/sandbox-url-detector.js +0 -7
  85. package/dist/session/outcomes/change-request-outcomes.js +24 -3
  86. package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
  87. package/dist/session/panel/advanced/advanced-panel.js +3 -7
  88. package/dist/session/panel/easy/app-preview.js +12 -2
  89. package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
  90. package/dist/session/panel/easy/easy-panel-logic.js +1 -1
  91. package/dist/session/panel/easy/outputs-card.js +2 -1
  92. package/dist/session/panel/easy/panel-card.js +5 -3
  93. package/dist/session/panel/easy/preview-load.d.ts +14 -0
  94. package/dist/session/panel/easy/preview-load.js +15 -0
  95. package/dist/session/panel/easy/step-detail-body.js +2 -7
  96. package/dist/session/panel/session-panel-provider.js +6 -2
  97. package/dist/session/panel/shared/use-panel-derivations.js +6 -4
  98. package/dist/session/panel/use-session-panel-layout.js +7 -5
  99. package/dist/session/print/session-print-header.js +9 -8
  100. package/dist/session/thread/session-chat.js +12 -9
  101. package/dist/session/thread/session-history.js +3 -1
  102. package/dist/session/thread/transcript.js +13 -7
  103. package/dist/session/tool/shared/file-list.js +19 -2
  104. package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
  105. package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
  106. package/dist/session/tool/shared/infrastructure-output.js +9 -5
  107. package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
  108. package/dist/session/tool/shared/infrastructure-rows.js +7 -5
  109. package/dist/session/tool/tools/bash-tool.js +10 -6
  110. package/dist/session/tool/tools/web-search-tool.js +25 -3
  111. package/dist/session/turn/recovered-failures.d.ts +8 -0
  112. package/dist/session/turn/recovered-failures.js +32 -0
  113. package/dist/session/turn/streaming-cadence.js +5 -8
  114. package/dist/styles.css +1 -1
  115. package/dist/terminal/session-terminal-panel.js +3 -5
  116. package/dist/theme/theme.js +2 -1
  117. package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
  118. package/dist/workspace/review/review-center.js +13 -10
  119. package/dist/workspace/session-workspace.js +5 -9
  120. package/dist/workspace/sessions/deleted-session-row.js +5 -2
  121. package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
  122. package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
  123. package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
  124. package/package.json +3 -3
@@ -94,11 +94,9 @@ export function SessionTerminalPanel({ sessionId, projectId, projectSessionId, h
94
94
  setOptimisticPty(null);
95
95
  setTerminalPty(sessionId, null);
96
96
  }, [sessionId, setTerminalPty]);
97
- useEffect(() => {
98
- if (listedPty && optimisticPty?.id === listedPty.id) {
99
- setOptimisticPty(null);
100
- }
101
- }, [listedPty, optimisticPty?.id]);
97
+ if (listedPty && optimisticPty?.id === listedPty.id) {
98
+ setOptimisticPty(null);
99
+ }
102
100
  useEffect(() => {
103
101
  if (!terminalPtyId || isLoading || !ptys || pty || optimisticPty?.id === terminalPtyId)
104
102
  return;
@@ -123,8 +123,9 @@ function checkName(name) {
123
123
  return name;
124
124
  }
125
125
  function steps(field, allowed, values) {
126
+ const allowedSet = new Set(allowed);
126
127
  return Object.entries(values ?? {}).flatMap(([k, v]) => {
127
- if (!allowed.includes(k))
128
+ if (!allowedSet.has(k))
128
129
  throw new Error(`@kortix/ui theme: unknown ${field} step "${k}"`);
129
130
  return v === undefined ? [] : [[k, v]];
130
131
  });
@@ -10,7 +10,7 @@ import { Github } from '../../../internal/icons/github.js';
10
10
  import { cn } from '../../../internal/lib/utils.js';
11
11
  import { CaretDownIcon as CaretDown, GitBranchIcon as GitBranch, MagnifyingGlassIcon as Search, } from '@phosphor-icons/react';
12
12
  import { useTranslations } from '../../../i18n/index.js';
13
- import { useEffect, useMemo, useState } from 'react';
13
+ import { useMemo, useState } from 'react';
14
14
  /** Varied so the placeholder reads as a list of names, not a barcode. */
15
15
  const SKELETON_WIDTHS = ['w-3/5', 'w-2/5', 'w-1/2', 'w-3/4', 'w-1/3'];
16
16
  /** Above this many options the list is fixed-height (see `SearchPicker`). */
@@ -28,13 +28,14 @@ function SearchPicker({ value, options, loading, disabled, loadingLabel, placeho
28
28
  const filtered = normalizedSearch
29
29
  ? options.filter((option) => option.keywords.includes(normalizedSearch))
30
30
  : options;
31
- useEffect(() => {
32
- if (!open) {
31
+ const handleOpenChange = (next) => {
32
+ setOpen(next);
33
+ if (!next) {
33
34
  setSearch('');
34
35
  onSearchChange?.('');
35
36
  }
36
- }, [onSearchChange, open]);
37
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, modal: false, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", disabled: disabled, className: cn(triggerVariants({ size: 'md' }), 'w-full'), children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground flex shrink-0 [&_svg]:size-4", children: icon }), _jsx("span", { className: cn('min-w-0 truncate', !selected && 'text-muted-foreground'), children: selected?.label ?? (firstLoad ? loadingLabel : placeholder) })] }), loading ? (_jsx(Loading, { variant: "spokes", className: "text-muted-foreground! size-4 shrink-0" })) : (_jsx(CaretDown, { className: cn(TRIGGER_CARET_CLASS, TRIGGER_ICON_SIZE.md) }))] }) }), _jsxs(PopoverContent, { side: "top", align: "start", avoidCollisions: false, className: "w-[var(--radix-popover-trigger-width)] overflow-hidden p-0", children: [_jsx("div", { className: "border-border border-b py-1", children: _jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(Search, {}) }), _jsx(InputGroupSearchInput, { size: "xs", className: "border-transparent focus:border-transparent focus:ring-0", value: search, onChange: (event) => {
37
+ };
38
+ return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, modal: false, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", disabled: disabled, className: cn(triggerVariants({ size: 'md' }), 'w-full'), children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground flex shrink-0 [&_svg]:size-4", children: icon }), _jsx("span", { className: cn('min-w-0 truncate', !selected && 'text-muted-foreground'), children: selected?.label ?? (firstLoad ? loadingLabel : placeholder) })] }), loading ? (_jsx(Loading, { variant: "spokes", className: "text-muted-foreground! size-4 shrink-0" })) : (_jsx(CaretDown, { className: cn(TRIGGER_CARET_CLASS, TRIGGER_ICON_SIZE.md) }))] }) }), _jsxs(PopoverContent, { side: "top", align: "start", avoidCollisions: false, className: "w-[var(--radix-popover-trigger-width)] overflow-hidden p-0", children: [_jsx("div", { className: "border-border border-b py-1", children: _jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(Search, {}) }), _jsx(InputGroupSearchInput, { size: "xs", className: "border-transparent focus:border-transparent focus:ring-0", value: search, onChange: (event) => {
38
39
  setSearch(event.target.value);
39
40
  onSearchChange?.(event.target.value);
40
41
  }, placeholder: searchPlaceholder, autoCapitalize: "none", autoCorrect: "off", autoFocus: true })] }) }), firstLoad ? (
@@ -49,7 +50,7 @@ function SearchPicker({ value, options, loading, disabled, loadingLabel, placeho
49
50
  const active = option.value === value;
50
51
  return (_jsx("li", { children: _jsxs("button", { type: "button", "aria-pressed": active, className: cn(menuRow('md', 'default'), 'text-left', option.description && 'items-start', active && 'bg-primary/10 text-foreground'), onClick: () => {
51
52
  onValueChange(option.value);
52
- setOpen(false);
53
+ handleOpenChange(false);
53
54
  }, children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "text-foreground truncate", children: option.label }), option.description ? (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: option.description })) : null] }), option.badge ? _jsx("span", { className: "shrink-0", children: option.badge }) : null] }) }, option.value));
54
55
  }) }))] })] }));
55
56
  }
@@ -347,15 +347,15 @@ export function ReviewCenter({ initialItems, segment: segmentProp, onSegmentChan
347
347
  setSelectMode(false);
348
348
  }
349
349
  // If the active session filter no longer has items in this segment, reset it.
350
- useEffect(() => {
351
- if (sessionFilter !== 'all' && !sessionOpts.some((s) => s.sessionId === sessionFilter)) {
352
- setSessionFilter('all');
353
- }
354
- }, [sessionOpts, sessionFilter]);
350
+ if (sessionFilter !== 'all' && !sessionOpts.some((s) => s.sessionId === sessionFilter)) {
351
+ setSessionFilter('all');
352
+ }
355
353
  // Keep the focus cursor in range as the visible list changes.
356
- useEffect(() => {
354
+ const [visibleCountSeen, setVisibleCountSeen] = useState(null);
355
+ if (visible.length !== visibleCountSeen) {
356
+ setVisibleCountSeen(visible.length);
357
357
  setFocusedIdx((i) => Math.max(0, Math.min(i, visible.length - 1)));
358
- }, [visible.length]);
358
+ }
359
359
  // Select mode: DOM focus follows the cursor (j/k, a toggled row), so Space
360
360
  // and Enter act on the row the user sees, and a pointer entry that
361
361
  // unmounts the clicked checkbox keeps focus on its row.
@@ -377,12 +377,14 @@ export function ReviewCenter({ initialItems, segment: segmentProp, onSegmentChan
377
377
  // mutation (there's no per-call settle callback threaded through `apply`),
378
378
  // so clear the pending marker here rather than leave a button spinning
379
379
  // forever if the item already left the list (e.g. a stale poll raced it).
380
- useEffect(() => {
380
+ const [initialItemsSeen, setInitialItemsSeen] = useState(initialItems);
381
+ if (initialItems !== initialItemsSeen) {
382
+ setInitialItemsSeen(initialItems);
381
383
  if (initialItems)
382
384
  setItems(initialItems);
383
385
  setPendingId(null);
384
386
  setPendingDecision(null);
385
- }, [initialItems]);
387
+ }
386
388
  // Detect items that arrived since the user last looked → flag them "fresh".
387
389
  // The first load of each segment seeds its known set silently (nothing is
388
390
  // "new" on open, or on a first visit to a segment).
@@ -711,9 +713,10 @@ export function ReviewCenter({ initialItems, segment: segmentProp, onSegmentChan
711
713
  },
712
714
  ];
713
715
  const confirmCount = bulkIds[confirm.verb].length;
716
+ const confirmIds = new Set(bulkIds[confirm.verb]);
714
717
  // Every item the verb will decide: which app, what it does, to what, how risky.
715
718
  const confirmList = (_jsx("ul", { className: "border-border divide-border max-h-72 w-full min-w-0 divide-y overflow-y-auto rounded-lg border", children: selectedItems
716
- .filter((i) => bulkIds[confirm.verb].includes(i.id))
719
+ .filter((i) => confirmIds.has(i.id))
717
720
  .map((i) => {
718
721
  const risk = RISK_BADGE[i.risk];
719
722
  // The arguments, as the approval modal reads them: the content
@@ -21,7 +21,7 @@ import { useKortixComputerStore } from '../internal/stores/kortix-computer-store
21
21
  import { sessionPreviewTabId, useSessionBrowserStore, } from '../internal/stores/session-browser-store.js';
22
22
  import { ProjectScope } from '../internal/project-scope.js';
23
23
  import { SessionThread } from '../session/thread/session-thread.js';
24
- import { memo, useCallback, useEffect, useState } from 'react';
24
+ import { memo, useCallback, useState } from 'react';
25
25
  export const SessionLayout = memo(function SessionLayout({ sessionId, projectId, projectSessionId, children, bootStage = null, transient = false, }) {
26
26
  const isMobile = useIsMobile();
27
27
  const booting = !!bootStage;
@@ -129,15 +129,11 @@ function useSessionPanelBody({ sessionId, projectId, projectSessionId, isEasy, e
129
129
  const showTerminal = !isEasy && effectiveView === 'terminal';
130
130
  const showAudit = !isEasy && effectiveView === 'audit';
131
131
  const [terminalActivated, setTerminalActivated] = useState(false);
132
- useEffect(() => {
133
- if (showTerminal)
134
- setTerminalActivated(true);
135
- }, [showTerminal]);
132
+ if (showTerminal && !terminalActivated)
133
+ setTerminalActivated(true);
136
134
  const [browserActivated, setBrowserActivated] = useState(false);
137
- useEffect(() => {
138
- if (showBrowser)
139
- setBrowserActivated(true);
140
- }, [showBrowser]);
135
+ if (showBrowser && !browserActivated)
136
+ setBrowserActivated(true);
141
137
  // Easy mode's body is the detail shell and nothing else — the cards moved to
142
138
  // the floating overlay over the chat. The Advanced branches below are kept
143
139
  // intact (Advanced is disabled, not deleted) and are unreachable while
@@ -5,6 +5,7 @@ import Loading from '../../primitives/loading.js';
5
5
  import { getSessionDisplayTitle } from '../../session/actions/project-session-list-helpers.js';
6
6
  import { useLocale, useTranslations } from '../../i18n/index.js';
7
7
  import { relativeTime } from '../../internal/lib/relative-time.js';
8
+ import { useMemo } from 'react';
8
9
  /**
9
10
  * One row of Recently deleted (KRTX-2853): a session the viewer deleted (or,
10
11
  * as a manager, one they can open), still inside its restore window. Not a
@@ -16,10 +17,12 @@ export function DeletedSessionRow({ session, restoring, onRestore, }) {
16
17
  const title = getSessionDisplayTitle(session);
17
18
  const deletedAt = session.deleted_at ? new Date(session.deleted_at) : null;
18
19
  const restoreUntil = session.restore_until ? new Date(session.restore_until) : null;
19
- const exact = (date) => new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }).format(date);
20
+ const exactFormat = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }), [locale]);
21
+ const dayFormat = useMemo(() => new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric' }), [locale]);
22
+ const exact = (date) => exactFormat.format(date);
20
23
  return (_jsxs("div", { className: "bg-popover flex items-center gap-3 rounded-md border px-3 py-2", "data-deleted-session-id": session.session_id, children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-foreground truncate text-sm", children: title }), _jsxs("p", { className: "text-muted-foreground truncate text-xs", children: [deletedAt ? (_jsx("time", { dateTime: session.deleted_at, title: exact(deletedAt), children: t('deletedAgo', {
21
24
  when: relativeTime(session.deleted_at, locale, undefined, { style: 'long' }),
22
25
  }) })) : null, deletedAt && restoreUntil ? ' · ' : null, restoreUntil ? (_jsx("time", { dateTime: session.restore_until, title: exact(restoreUntil), children: t('restorableUntil', {
23
- date: new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric' }).format(restoreUntil),
26
+ date: dayFormat.format(restoreUntil),
24
27
  }) })) : null] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: restoring, onClick: () => onRestore(session), "aria-label": t('restoreNamed', { title }), children: [restoring ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, t('restore')] })] }));
25
28
  }
@@ -2,13 +2,14 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { PromoNavRow } from './promo-nav-row.js';
4
4
  import { LaptopIcon } from '@phosphor-icons/react';
5
- import { useCallback, useState } from 'react';
5
+ import { useCallback, useState, useSyncExternalStore } from 'react';
6
6
  import Loading from '../../../primitives/loading.js';
7
7
  import { useSidebar } from '../../../primitives/sidebar.js';
8
8
  import { useConnectDesktopComputer, useOwnsPairedComputer, useThisComputerState, } from '../../../internal/tunnel/computer-connect.js';
9
9
  import { useIsMobile } from '../../../internal/hooks/index.js';
10
10
  import { useTranslations } from '../../../i18n/index.js';
11
11
  export const COMPUTER_PROMO_DISMISSED_KEY = 'kortix.computer-promo.dismissed';
12
+ const subscribeNoop = () => () => { };
12
13
  function readDismissed() {
13
14
  try {
14
15
  return window.localStorage.getItem(COMPUTER_PROMO_DISMISSED_KEY) === '1';
@@ -30,9 +31,10 @@ function readDismissed() {
30
31
  * and closing the sheet unmounts it together with anything it renders.
31
32
  */
32
33
  export function ProjectComputerNavItem({ projectId, onOpenConnect, }) {
33
- // Client-only: the row renders nothing until its queries resolve, so the
34
- // server render and the first client render agree.
35
- const [dismissed, setDismissed] = useState(() => typeof window === 'undefined' ? true : readDismissed());
34
+ // The server and the hydrating render read "dismissed"; the stored flag applies after.
35
+ const stored = useSyncExternalStore(subscribeNoop, readDismissed, () => true);
36
+ const [dismissedNow, setDismissed] = useState(false);
37
+ const dismissed = stored || dismissedNow;
36
38
  const dismiss = useCallback(() => {
37
39
  setDismissed(true);
38
40
  try {
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { PromoNavRow } from './promo-nav-row.js';
4
- import { useCallback, useState } from 'react';
4
+ import { useCallback, useState, useSyncExternalStore } from 'react';
5
5
  import { useSidebar } from '../../../primitives/sidebar.js';
6
6
  import { Mcp } from '../../../internal/icons/mcp.js';
7
7
  import { useIsMobile } from '../../../internal/hooks/index.js';
8
8
  import { useTranslations } from '../../../i18n/index.js';
9
9
  export const MCP_PROMO_DISMISSED_KEY = 'kortix.mcp-promo.dismissed';
10
+ const subscribeNoop = () => () => { };
10
11
  function readDismissed() {
11
12
  try {
12
13
  return window.localStorage.getItem(MCP_PROMO_DISMISSED_KEY) === '1';
@@ -26,7 +27,10 @@ export function ProjectMcpNavItem({ onOpenConnect }) {
26
27
  const tComputers = useTranslations('computers');
27
28
  const isMobile = useIsMobile();
28
29
  const { setOpenMobile } = useSidebar();
29
- const [dismissed, setDismissed] = useState(() => typeof window === 'undefined' ? true : readDismissed());
30
+ // The server and the hydrating render read "dismissed"; the stored flag applies after.
31
+ const stored = useSyncExternalStore(subscribeNoop, readDismissed, () => true);
32
+ const [dismissedNow, setDismissed] = useState(false);
33
+ const dismissed = stored || dismissedNow;
30
34
  const dismiss = useCallback(() => {
31
35
  setDismissed(true);
32
36
  try {
@@ -5,15 +5,20 @@ import { Input } from '../../../primitives/input.js';
5
5
  import Loading from '../../../primitives/loading.js';
6
6
  import { Modal, ModalBody, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, } from '../../../primitives/modal.js';
7
7
  import { useTranslations } from '../../../i18n/index.js';
8
- import { useEffect, useState } from 'react';
8
+ import { useState } from 'react';
9
9
  import { MAX_SESSION_NAME_LENGTH, useRenameSession } from '../../../session/actions/use-rename-session.js';
10
10
  export function RenameSessionModal({ projectId, sessionId, currentName, open, onOpenChange, onSaved, }) {
11
11
  const tI18nHardcoded = useTranslations('hardcodedUi');
12
12
  const [value, setValue] = useState(currentName ?? '');
13
- useEffect(() => {
13
+ const [syncedFor, setSyncedFor] = useState([
14
+ open,
15
+ currentName,
16
+ ]);
17
+ if (syncedFor[0] !== open || syncedFor[1] !== currentName) {
18
+ setSyncedFor([open, currentName]);
14
19
  if (open)
15
20
  setValue(currentName ?? '');
16
- }, [open, currentName]);
21
+ }
17
22
  const renameMutation = useRenameSession(projectId, sessionId, {
18
23
  onSuccess: () => {
19
24
  onSaved?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortix/ui",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
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",
@@ -2157,7 +2157,7 @@
2157
2157
  "@extend-ai/react-docx": "^0.7.5",
2158
2158
  "@extend-ai/react-xlsx": "0.13.4",
2159
2159
  "@glideapps/glide-data-grid": "6.0.4-alpha24",
2160
- "@kortix/llm-catalog": "0.14.5",
2160
+ "@kortix/llm-catalog": "0.14.6",
2161
2161
  "@lezer/highlight": "1.2.3",
2162
2162
  "@paper-design/shaders-react": "^0.0.76",
2163
2163
  "@phosphor-icons/react": "2.1.10",
@@ -2246,7 +2246,7 @@
2246
2246
  "zustand": "^5.0.3"
2247
2247
  },
2248
2248
  "peerDependencies": {
2249
- "@kortix/sdk": "^0.14.5",
2249
+ "@kortix/sdk": "^0.14.6",
2250
2250
  "@tanstack/react-query": "^5.75.2",
2251
2251
  "@types/react": ">=18",
2252
2252
  "react": ">=19",