@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
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
3
  import { OWN_SCROLL_MS, chevronVisible, isAtEnd, keyScrollIntentFor, pickAnchorIndex, prefersReducedMotion, roomUnderNewestTurn, settleMotion, shouldReleaseFollow, } from './use-auto-scroll-physics.js';
4
4
  export { AT_END_PX, BOTTOM_GAP_PX, CHEVRON_PX, GLIDE_MIN_PX, OWN_SCROLL_MS, TURN_TOP_OFFSET, chevronVisible, classifyScrollKey, isAtEnd, isEditableTarget, isSpaceActivatedTarget, keyScrollIntentFor, pickAnchorIndex, roomUnderNewestTurn, settleMotion, shouldReleaseFollow, } from './use-auto-scroll-physics.js';
5
5
  /**
@@ -234,7 +234,9 @@ export function useAutoScroll({ hasContent = false, } = {}) {
234
234
  markOwnScroll(OWN_SCROLL_MS);
235
235
  el.scrollTop = end;
236
236
  }, [sizeRoom, glideTo, markOwnScroll]);
237
- settleRef.current = settle;
237
+ useLayoutEffect(() => {
238
+ settleRef.current = settle;
239
+ }, [settle]);
238
240
  const goToEnd = useCallback((behavior) => {
239
241
  const el = scrollRef.current;
240
242
  if (!el)
@@ -455,7 +457,7 @@ export function useAutoScroll({ hasContent = false, } = {}) {
455
457
  document.removeEventListener('keydown', onKeyDown, { capture: true });
456
458
  el.removeEventListener('scroll', onScroll);
457
459
  };
458
- }, [goToEnd, endGlide, hasContent, setFollow, markOwnScroll]);
460
+ }, [goToEnd, endGlide, hasContent, setFollow]);
459
461
  return {
460
462
  scrollRef,
461
463
  contentRef,
@@ -3,7 +3,13 @@ import type { QueryClient } from '@tanstack/react-query';
3
3
  export declare function registerQueryClient(client: QueryClient): void;
4
4
  export declare function getSharedQueryClient(): QueryClient | null;
5
5
  /**
6
- * Run `run` with the shared client — now if it is registered, else as soon as
7
- * it registers. A one-shot: it fires once and is forgotten.
6
+ * Run `run` with the shared client now (if one is registered), and again with
7
+ * every DIFFERENT client registered later. Returns the unsubscribe.
8
+ *
9
+ * AuthProvider sits ABOVE ReactQueryProvider, and #10344 mounts the app shell
10
+ * one auth round trip sooner, so a caller can ask before any client exists.
11
+ * `ReactQueryProvider` registers during render, and React can discard that
12
+ * render (a hydration retry, a suspension, Strict Mode) and commit a second
13
+ * client: a caller that ran once would hold a client the app no longer reads.
8
14
  */
9
- export declare function whenQueryClientReady(run: (client: QueryClient) => void): void;
15
+ export declare function onEachQueryClient(run: (client: QueryClient) => void): () => void;
@@ -8,27 +8,33 @@
8
8
  * from the central reset helper.
9
9
  */
10
10
  let sharedQueryClient = null;
11
- let waiters = [];
11
+ /** `onEachQueryClient` subscribers. replica-local: one tab's app. */
12
+ const followers = new Set();
12
13
  export function registerQueryClient(client) {
14
+ if (sharedQueryClient === client)
15
+ return;
13
16
  sharedQueryClient = client;
14
- // AuthProvider sits ABOVE ReactQueryProvider, and #10344 mounts the app
15
- // shell one auth round trip sooner, so `adoptDeviceCaches` can run before
16
- // this registers. Flush anyone who asked for the client before it existed.
17
- const pending = waiters;
18
- waiters = [];
19
- for (const run of pending)
17
+ for (const run of [...followers])
20
18
  run(client);
21
19
  }
22
20
  export function getSharedQueryClient() {
23
21
  return sharedQueryClient;
24
22
  }
25
23
  /**
26
- * Run `run` with the shared client — now if it is registered, else as soon as
27
- * it registers. A one-shot: it fires once and is forgotten.
24
+ * Run `run` with the shared client now (if one is registered), and again with
25
+ * every DIFFERENT client registered later. Returns the unsubscribe.
26
+ *
27
+ * AuthProvider sits ABOVE ReactQueryProvider, and #10344 mounts the app shell
28
+ * one auth round trip sooner, so a caller can ask before any client exists.
29
+ * `ReactQueryProvider` registers during render, and React can discard that
30
+ * render (a hydration retry, a suspension, Strict Mode) and commit a second
31
+ * client: a caller that ran once would hold a client the app no longer reads.
28
32
  */
29
- export function whenQueryClientReady(run) {
33
+ export function onEachQueryClient(run) {
34
+ followers.add(run);
30
35
  if (sharedQueryClient)
31
36
  run(sharedQueryClient);
32
- else
33
- waiters.push(run);
37
+ return () => {
38
+ followers.delete(run);
39
+ };
34
40
  }
@@ -44,10 +44,11 @@ export function newArrivals(seen, rows) {
44
44
  * is not.
45
45
  */
46
46
  export function planArrivals(fresh, context) {
47
+ const unseen = new Set(context.unseen);
47
48
  const announce = fresh
48
49
  .filter((row) => !row.session_id ||
49
50
  (!context.onScreen(row.session_id) &&
50
- !(row.kind === 'turn_done' && context.unseen.includes(row.session_id))))
51
+ !(row.kind === 'turn_done' && unseen.has(row.session_id))))
51
52
  .slice(0, MAX_ARRIVAL_ALERTS);
52
53
  return context.os ? { toast: [], os: announce } : { toast: announce, os: [] };
53
54
  }
@@ -49,6 +49,11 @@ export interface OutcomeAction {
49
49
  intent: 'open' | 'link';
50
50
  /** Required when `intent` is `link`; ignored otherwise. */
51
51
  href?: string;
52
+ /**
53
+ * For `open`: the source id the modal opens when it is not this outcome's
54
+ * own. A superseded change request opens its replacement (KRTX-3031).
55
+ */
56
+ target?: string;
52
57
  }
53
58
  export interface Outcome {
54
59
  /**
@@ -16,13 +16,13 @@ import { isShareLive, publicShareUrl, shareListState, useRevokePublicShare, useS
16
16
  import { cn } from '../../lib/utils.js';
17
17
  function ShareRow({ share, onRevoke, isRevoking, }) {
18
18
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
19
- const tTranscript = useTranslations('hardcodedUi.publicTranscriptShare');
20
19
  const [copied, setCopied] = useState(false);
21
20
  const live = isShareLive(share);
22
21
  // Read at render time: `publicShareUrl` is null outside a browser (SSR).
23
22
  const url = publicShareUrl(share.public_path);
24
23
  const isFile = share.resource_type === 'file';
25
- // A transcript share has no port and no file: it names the conversation.
24
+ // A legacy transcript share (revoked; the feature is removed) still carries
25
+ // no port and no file: its label is the row's own meta.
26
26
  const isTranscript = share.resource_type === 'transcript';
27
27
  const Icon = isFile ? FileText : isTranscript ? ChatText : Globe;
28
28
  const copy = async () => {
@@ -33,11 +33,7 @@ function ShareRow({ share, onRevoke, isRevoking, }) {
33
33
  successToast(tI18nComplete.raw('textd0f24de8dbc6'));
34
34
  setTimeout(() => setCopied(false), 2000);
35
35
  };
36
- return (_jsxs("li", { className: cn('group bg-popover flex items-center gap-3 rounded-md border px-4 py-2.5 transition-colors', !live && 'opacity-60'), children: [_jsx("span", { className: cn('flex size-9 shrink-0 items-center justify-center rounded-sm', live ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(Icon, { className: cn('size-5', live ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "truncate text-sm font-medium", children: share.label }), !live && (_jsx(Badge, { variant: "outline", size: "xs", children: share.revoked_at ? 'Revoked' : 'Expired' }))] }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: isFile
37
- ? share.file_path
38
- : isTranscript
39
- ? tTranscript('rowMeta')
40
- : `Port ${share.port}${share.path}` })] }), live && (_jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [_jsx(Hint, { label: copied ? 'Copied' : tI18nComplete.raw('textdbf362d4f210'), side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": tI18nComplete.raw('textdbf362d4f210'), disabled: !url, onClick: () => void copy(), className: "size-8 active:scale-[0.96]", children: copied ? (_jsx(Check, { className: "text-kortix-green size-4" })) : (_jsx(Link2, { className: "size-4" })) }) }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: onRevoke, disabled: isRevoking, children: [isRevoking ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, tI18nComplete.raw('text87e6d00bbf53')] })] }))] }));
36
+ return (_jsxs("li", { className: cn('group bg-popover flex items-center gap-3 rounded-md border px-4 py-2.5 transition-colors', !live && 'opacity-60'), children: [_jsx("span", { className: cn('flex size-9 shrink-0 items-center justify-center rounded-sm', live ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(Icon, { className: cn('size-5', live ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "truncate text-sm font-medium", children: share.label }), !live && (_jsx(Badge, { variant: "outline", size: "xs", children: share.revoked_at ? 'Revoked' : 'Expired' }))] }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: isFile ? share.file_path : isTranscript ? share.label : `Port ${share.port}${share.path}` })] }), live && (_jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [_jsx(Hint, { label: copied ? 'Copied' : tI18nComplete.raw('textdbf362d4f210'), side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": tI18nComplete.raw('textdbf362d4f210'), disabled: !url, onClick: () => void copy(), className: "size-8 active:scale-[0.96]", children: copied ? (_jsx(Check, { className: "text-kortix-green size-4" })) : (_jsx(Link2, { className: "size-4" })) }) }), _jsxs(Button, { variant: "ghost", size: "sm", onClick: onRevoke, disabled: isRevoking, children: [isRevoking ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, tI18nComplete.raw('text87e6d00bbf53')] })] }))] }));
41
37
  }
42
38
  export function SessionSharesModal({ projectId, sessionId, open, onOpenChange, }) {
43
39
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
@@ -173,6 +173,20 @@ export const useKortixComputerStore = create()(devtools((set, get) => ({
173
173
  get().setIsActionPanelOpen(true);
174
174
  },
175
175
  setActiveSession: (sessionId, continuity) => {
176
+ const nextProjectSessionId = continuity?.projectSessionId ?? null;
177
+ const nextIsTransient = continuity?.transient ?? false;
178
+ // The boot shell of the session whose real chat already owns the right
179
+ // side. The two layers mount in either order, and the chat holds the
180
+ // panel's content: a shell that took over kept the right side after it
181
+ // faded out, so ⌘I reopened the cards instead of the detail it had
182
+ // just hidden (KRTX-2944). Not a session change, so nothing moves.
183
+ if (nextIsTransient &&
184
+ nextProjectSessionId !== null &&
185
+ get()._activeSessionId !== null &&
186
+ !get()._activeIsTransient &&
187
+ get()._activeProjectSessionId === nextProjectSessionId) {
188
+ return;
189
+ }
176
190
  // A quick-view is an intent about the session it was made in — it must
177
191
  // not replay when some other session mounts later. Cleared even on the
178
192
  // no-op re-activation path below, or a request planted for another
@@ -182,8 +196,6 @@ export const useKortixComputerStore = create()(devtools((set, get) => ({
182
196
  set({ pendingQuickView: null });
183
197
  }
184
198
  const prev = get()._activeSessionId;
185
- const nextProjectSessionId = continuity?.projectSessionId ?? null;
186
- const nextIsTransient = continuity?.transient ?? false;
187
199
  if (prev === sessionId) {
188
200
  // Same layout re-activating (e.g. the shell flipping transient→ready
189
201
  // state, or a tab refocus): keep the continuity bookkeeping fresh so
@@ -59,6 +59,38 @@ interface SessionHoverState {
59
59
  closeSession: (sessionId: string) => void;
60
60
  /** Immediate, for a click that opens something else and for unmount. */
61
61
  dismiss: () => void;
62
+ /**
63
+ * The row whose session a click just opened. Its card would only describe
64
+ * the page on screen, so it stays shut until the pointer leaves the row.
65
+ * Unselected, like `warm`.
66
+ */
67
+ suppressedSessionId: string | null;
68
+ suppress: (sessionId: string) => void;
69
+ release: (sessionId: string) => void;
62
70
  }
63
71
  export declare const useSessionHoverStore: import("zustand").UseBoundStore<import("zustand").StoreApi<SessionHoverState>>;
72
+ type RowTarget = {
73
+ matches?: (selector: string) => boolean;
74
+ };
75
+ /**
76
+ * A session row's hover wiring (KRTX-2847). The press focuses the row and the
77
+ * pointer rests on it, so without the click handler every switch showed the
78
+ * card of the session just opened, 200 ms later, until the page took focus.
79
+ * Focus leaving the row releases it only when the pointer is elsewhere: the
80
+ * new page takes focus while the pointer still rests on the row.
81
+ */
82
+ export declare function sessionRowHoverHandlers(id: string): {
83
+ onPointerEnter: (event: {
84
+ pointerType: string;
85
+ currentTarget: RowTarget;
86
+ }) => void;
87
+ onPointerLeave: () => void;
88
+ onFocus: (event: {
89
+ currentTarget: RowTarget;
90
+ }) => void;
91
+ onBlur: (event: {
92
+ currentTarget: RowTarget;
93
+ }) => void;
94
+ onClick: () => void;
95
+ };
64
96
  export {};
@@ -65,8 +65,11 @@ export const useSessionHoverStore = create((set, get) => ({
65
65
  via: 'pointer',
66
66
  slot: null,
67
67
  warm: false,
68
+ suppressedSessionId: null,
68
69
  setSlot: (slot) => set({ slot }),
69
70
  openSession: (sessionId, anchor, via = 'pointer') => {
71
+ if (get().suppressedSessionId === sessionId)
72
+ return;
70
73
  closeTimer = cancel(closeTimer);
71
74
  warmTimer = cancel(warmTimer);
72
75
  // A pending open belongs to whichever row the pointer has just left.
@@ -106,4 +109,39 @@ export const useSessionHoverStore = create((set, get) => ({
106
109
  warmTimer = cancel(warmTimer);
107
110
  set({ activeSessionId: null, warm: false });
108
111
  },
112
+ suppress: (sessionId) => {
113
+ set({ suppressedSessionId: sessionId });
114
+ get().dismiss();
115
+ },
116
+ release: (sessionId) => {
117
+ if (get().suppressedSessionId === sessionId)
118
+ set({ suppressedSessionId: null });
119
+ },
109
120
  }));
121
+ /**
122
+ * A session row's hover wiring (KRTX-2847). The press focuses the row and the
123
+ * pointer rests on it, so without the click handler every switch showed the
124
+ * card of the session just opened, 200 ms later, until the page took focus.
125
+ * Focus leaving the row releases it only when the pointer is elsewhere: the
126
+ * new page takes focus while the pointer still rests on the row.
127
+ */
128
+ export function sessionRowHoverHandlers(id) {
129
+ const store = () => useSessionHoverStore.getState();
130
+ return {
131
+ onPointerEnter: (event) => {
132
+ if (event.pointerType !== 'touch')
133
+ store().openSession(id, event.currentTarget, 'pointer');
134
+ },
135
+ onPointerLeave: () => {
136
+ store().release(id);
137
+ store().closeSession(id);
138
+ },
139
+ onFocus: (event) => store().openSession(id, event.currentTarget, 'focus'),
140
+ onBlur: (event) => {
141
+ if (!event.currentTarget.matches?.(':hover'))
142
+ store().release(id);
143
+ store().closeSession(id);
144
+ },
145
+ onClick: () => store().suppress(id),
146
+ };
147
+ }
@@ -8,7 +8,7 @@
8
8
  * (`customize-sidebar-modal.tsx` syncs it). A row a project does not offer (a flag is off, a permission is denied)
9
9
  * keeps its place here and simply does not render.
10
10
  */
11
- export declare const SIDEBAR_NAV_ITEM_IDS: readonly ["customize", "apps", "files", "rooms"];
11
+ export declare const SIDEBAR_NAV_ITEM_IDS: readonly ["customize", "apps", "files", "rooms", "goals"];
12
12
  export type SidebarNavItemId = (typeof SIDEBAR_NAV_ITEM_IDS)[number];
13
13
  /**
14
14
  * `never` moves the row into the More menu (`project-nav-items.tsx`), so its
@@ -13,7 +13,7 @@ import { registerPersistedStore, resetPersistedStore } from './persisted-store-r
13
13
  */
14
14
  // Review (2026-10-17) and Reminders (2026-10-19) left the sidebar for Customize
15
15
  // tabs. A saved layout that still names one drops the id in `normalizeSidebarNav`.
16
- export const SIDEBAR_NAV_ITEM_IDS = ['customize', 'apps', 'files', 'rooms'];
16
+ export const SIDEBAR_NAV_ITEM_IDS = ['customize', 'apps', 'files', 'rooms', 'goals'];
17
17
  /** The layout before anyone customizes it. */
18
18
  export const SIDEBAR_NAV_DEFAULTS = [
19
19
  { id: 'customize', visibility: 'always' },
@@ -22,6 +22,8 @@ export const SIDEBAR_NAV_DEFAULTS = [
22
22
  // Rooms (2026-10-19, flag `agent_rooms`): chat rooms for sessions and people.
23
23
  // In the More menu until the person pins it (2026-10-20).
24
24
  { id: 'rooms', visibility: 'never' },
25
+ // Goals (2026-10-21, flag `goals`): objectives agents own until verified done.
26
+ { id: 'goals', visibility: 'always' },
25
27
  ];
26
28
  function isItemId(value) {
27
29
  return SIDEBAR_NAV_ITEM_IDS.includes(value);
@@ -22,6 +22,8 @@ export interface UiRoutes {
22
22
  apps: (projectId: string) => string | null;
23
23
  /** The project's chat rooms (flag `agent_rooms`). */
24
24
  rooms: (projectId: string) => string | null;
25
+ /** The project's goals (flag `goals`); a goal is `<goals>/<scope>/<slug>`. */
26
+ goals: (projectId: string) => string | null;
25
27
  members: (projectId: string) => string | null;
26
28
  /** The project settings page, on its sandbox section. */
27
29
  sandboxSettings: (projectId: string) => string | null;
@@ -6,6 +6,7 @@ const DEFAULT_ROUTES = {
6
6
  drive: (p) => `/projects/${p}/drive`,
7
7
  apps: (p) => `/projects/${p}/apps`,
8
8
  rooms: (p) => `/projects/${p}/rooms`,
9
+ goals: (p) => `/projects/${p}/goals`,
9
10
  members: (p) => `/projects/${p}/members`,
10
11
  sandboxSettings: (p) => `/projects/${p}/config?section=sandbox`,
11
12
  customize: (p, path) => `/projects/${p}/customize${path ? `/${path}` : ''}`,
@@ -12,6 +12,7 @@ import { useSessionBrowserStore } from '../../internal/stores/session-browser-st
12
12
  import { useUiLink } from '../../providers/ui-provider.js';
13
13
  import { useCallback, useEffect, useRef, useState } from 'react';
14
14
  import { useInsideLink } from './inside-link-context.js';
15
+ import { outputPathForName, useOutputPaths } from './output-paths-context.js';
15
16
  import { childrenToText } from './children-text.js';
16
17
  // Server-safe by design — see `inline-chip.tsx`. Re-exported so the client
17
18
  // consumers that already import them from here keep working.
@@ -114,6 +115,7 @@ export function ClickableInlineCode({ children }) {
114
115
  const { proxyUrl } = useSandboxProxy();
115
116
  const openPreview = useOpenPreviewLink();
116
117
  const insideLink = useInsideLink();
118
+ const outputPaths = useOutputPaths();
117
119
  const text = childrenToText(children).trim();
118
120
  // Before the URL and path checks: a hex is neither, and the test is a single
119
121
  // anchored regex against a string that is at most nine characters.
@@ -146,5 +148,11 @@ export function ClickableInlineCode({ children }) {
146
148
  if (looksLikeFilePath(text)) {
147
149
  return _jsx(FilePathCode, { text: text, children: children });
148
150
  }
151
+ // A bare filename (`claims.md`) is no path, but it may name one of this
152
+ // session's outputs: open that file, still showing the name.
153
+ const outputPath = outputPathForName(text, outputPaths);
154
+ if (outputPath) {
155
+ return _jsx(FilePathCode, { text: outputPath, children: children });
156
+ }
149
157
  return _jsx("code", { className: INLINE_CODE, children: children });
150
158
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The paths of the files this session produced: its Outputs.
3
+ *
4
+ * A reply names its deliverables by bare filename (`claims.md`), and a bare
5
+ * name is not a path, so it never linked. Opening it took Expand pane →
6
+ * Outputs → the file (KRTX-3023). Inline code that names exactly one of these
7
+ * outputs now opens it.
8
+ *
9
+ * Provider: `SessionPanelProvider`. Empty outside a session: nothing extra links.
10
+ */
11
+ export declare const OutputPathsContext: import("react").Context<readonly string[]>;
12
+ export declare const useOutputPaths: () => readonly string[];
13
+ /**
14
+ * The output a bare filename names, or null: it is not a filename, no output
15
+ * has that name, or two different outputs share it and the reply does not say
16
+ * which.
17
+ */
18
+ export declare function outputPathForName(text: string, paths: readonly string[]): string | null;
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+ import { createContext, useContext } from 'react';
3
+ /**
4
+ * The paths of the files this session produced: its Outputs.
5
+ *
6
+ * A reply names its deliverables by bare filename (`claims.md`), and a bare
7
+ * name is not a path, so it never linked. Opening it took Expand pane →
8
+ * Outputs → the file (KRTX-3023). Inline code that names exactly one of these
9
+ * outputs now opens it.
10
+ *
11
+ * Provider: `SessionPanelProvider`. Empty outside a session: nothing extra links.
12
+ */
13
+ export const OutputPathsContext = createContext([]);
14
+ export const useOutputPaths = () => useContext(OutputPathsContext);
15
+ const BARE_FILENAME = /^[\w@][\w@.-]*\.[A-Za-z0-9]+$/;
16
+ /** `/workspace/a/b.md` and `a/b.md` are one file. */
17
+ const sameFileKey = (path) => path.replace(/^\/workspace\//, '');
18
+ /**
19
+ * The output a bare filename names, or null: it is not a filename, no output
20
+ * has that name, or two different outputs share it and the reply does not say
21
+ * which.
22
+ */
23
+ export function outputPathForName(text, paths) {
24
+ if (!BARE_FILENAME.test(text))
25
+ return null;
26
+ let found = null;
27
+ for (const path of paths) {
28
+ if (path.slice(path.lastIndexOf('/') + 1) !== text)
29
+ continue;
30
+ if (found !== null && sameFileKey(found) !== sameFileKey(path))
31
+ return null;
32
+ found ??= path;
33
+ }
34
+ return found;
35
+ }
@@ -25,7 +25,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
25
25
  */
26
26
  // eslint-disable-next-line no-restricted-imports -- screen-reader text for the figure; this module is reached only through lazy()
27
27
  import { genuiA11yText } from '@kortix/sdk/genui';
28
- import { useState } from 'react';
28
+ import { useMemo, useState } from 'react';
29
29
  import { Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, XAxis, YAxis } from 'recharts';
30
30
  import { ChartContainer, ChartTooltip, ChartTooltipContent } from '../../../primitives/chart.js';
31
31
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../../../primitives/dialog.js';
@@ -95,7 +95,7 @@ function PieFigure({ variant, slices, total, unit, format, tooltip, focus, }) {
95
95
  const restTotal = rest.reduce((sum, slice) => sum + slice.value, 0);
96
96
  const block = (slice, i) => {
97
97
  const small = i > 0 && slice.value / restTotal < 0.12;
98
- return (_jsxs(HoverCard, { openDelay: 80, closeDelay: 40, children: [_jsx(HoverCardTrigger, { asChild: true, children: _jsx("div", { tabIndex: 0, onMouseEnter: () => focus.hover(slice.key), onMouseLeave: () => focus.hover(null), onClick: () => focus.toggle(slice.key), "aria-label": `${slice.label}: ${format(slice.value)} (${slice.percent}%)`, className: cn('focus-visible:ring-ring duration-fast flex min-h-1.5 min-w-0 flex-col justify-between overflow-hidden rounded-md transition-opacity outline-none focus-visible:ring-2', small ? 'p-0' : 'p-3', i < 2 ? 'text-background' : 'text-foreground'), style: { backgroundColor: PIE_RAMP[i], flexGrow: slice.value, flexBasis: 0, opacity: dimmed(focus, slice.key) }, children: small ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: "truncate text-xs", children: slice.label }), _jsxs("span", { className: cn('truncate tabular-nums', i === 0 ? 'text-2xl font-medium' : 'text-sm'), children: [slice.percent, "%"] })] })) }) }), _jsxs(HoverCardContent, { side: "top", className: "flex w-auto min-w-40 flex-col gap-1 p-3", children: [_jsxs("span", { className: "text-foreground flex items-center gap-2 text-sm font-medium", children: [dot(slice.fill), slice.label] }), _jsxs("span", { className: "text-muted-foreground font-mono text-xs tabular-nums", children: [unit ? `${format(slice.value)} ${unit}` : format(slice.value), " \u00B7 ", slice.percent, "%"] })] })] }, slice.key));
98
+ return (_jsxs(HoverCard, { openDelay: 80, closeDelay: 40, children: [_jsx(HoverCardTrigger, { asChild: true, children: _jsx("button", { type: "button", onMouseEnter: () => focus.hover(slice.key), onMouseLeave: () => focus.hover(null), onClick: () => focus.toggle(slice.key), "aria-label": `${slice.label}: ${format(slice.value)} (${slice.percent}%)`, className: cn('focus-visible:ring-ring duration-fast flex min-h-1.5 min-w-0 flex-col justify-between overflow-hidden rounded-md text-left transition-opacity outline-none focus-visible:ring-2', small ? 'p-0' : 'p-3', i < 2 ? 'text-background' : 'text-foreground'), style: { backgroundColor: PIE_RAMP[i], flexGrow: slice.value, flexBasis: 0, opacity: dimmed(focus, slice.key) }, children: small ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: "truncate text-xs", children: slice.label }), _jsxs("span", { className: cn('truncate tabular-nums', i === 0 ? 'text-2xl font-medium' : 'text-sm'), children: [slice.percent, "%"] })] })) }) }), _jsxs(HoverCardContent, { side: "top", className: "flex w-auto min-w-40 flex-col gap-1 p-3", children: [_jsxs("span", { className: "text-foreground flex items-center gap-2 text-sm font-medium", children: [dot(slice.fill), slice.label] }), _jsxs("span", { className: "text-muted-foreground font-mono text-xs tabular-nums", children: [unit ? `${format(slice.value)} ${unit}` : format(slice.value), " \u00B7 ", slice.percent, "%"] })] })] }, slice.key));
99
99
  };
100
100
  return (_jsxs("div", { className: "flex min-h-0 flex-1 gap-1", children: [block(first, 0), rest.length > 0 ? (_jsx("div", { className: "flex min-w-0 flex-col gap-1", style: { flexGrow: restTotal, flexBasis: 0 }, children: rest.map((slice, i) => block(slice, i + 1)) })) : null] }));
101
101
  }
@@ -118,11 +118,11 @@ export function ChartView({ node, props }) {
118
118
  const t = useTranslations('genui');
119
119
  const focus = useFocus();
120
120
  const locale = useLocale();
121
+ const number = useMemo(() => new Intl.NumberFormat(locale), [locale]);
122
+ const compact = useMemo(() => new Intl.NumberFormat(locale, { notation: 'compact' }), [locale]);
121
123
  // A streamed pie arrives before its slices resolve (the parent line comes first). Hold its space until one lands.
122
124
  if (node.type === 'PieChart' && kids(props.slices).length === 0)
123
125
  return GenuiPending(node);
124
- const number = new Intl.NumberFormat(locale);
125
- const compact = new Intl.NumberFormat(locale, { notation: 'compact' });
126
126
  const format = (value) => (typeof value === 'number' ? number.format(value) : value == null ? '—' : String(value));
127
127
  // The tooltip row of `ChartTooltipContent`, with the table's number format.
128
128
  const tooltip = (_jsx(ChartTooltipContent, { hideLabel: node.type === 'PieChart', formatter: (value, name, item) => (_jsxs(_Fragment, { children: [_jsx("span", { className: "size-2.5 shrink-0 rounded-xs", style: { backgroundColor: item.payload?.fill ?? item.color } }), _jsxs("span", { className: "flex flex-1 items-center justify-between gap-2 leading-none", children: [_jsx("span", { className: "text-muted-foreground", children: name }), _jsx("span", { className: "text-foreground font-mono font-medium tabular-nums", children: format(value) })] })] })) }));
@@ -160,7 +160,8 @@ export function ChartView({ node, props }) {
160
160
  /** The chart's values as text: the Show data dialog's table. A missing value stays a dash, never a zero. */
161
161
  export function ChartDataTable({ node }) {
162
162
  const t = useTranslations('genui');
163
- const number = new Intl.NumberFormat(useLocale());
163
+ const locale = useLocale();
164
+ const number = useMemo(() => new Intl.NumberFormat(locale), [locale]);
164
165
  const format = (value) => (typeof value === 'number' ? number.format(value) : value == null ? '—' : String(value));
165
166
  const { props } = node;
166
167
  let head;
@@ -163,7 +163,8 @@ function MapMarker({ longitude, latitude, label, children, ...markerOptions }) {
163
163
  marker.setLngLat([longitude, latitude]);
164
164
  }
165
165
  }, [marker, longitude, latitude]);
166
- return _jsx(MarkerContext.Provider, { value: { marker, map }, children: children });
166
+ const markerContextValue = useMemo(() => ({ marker, map }), [marker, map]);
167
+ return _jsx(MarkerContext.Provider, { value: markerContextValue, children: children });
167
168
  }
168
169
  function MarkerContent({ children, className }) {
169
170
  const { marker } = useMarkerContext();
@@ -22,10 +22,12 @@ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
22
22
  const PreviewImageContent = React.forwardRef(({ className, fileContent, fileName, ...props }, ref) => {
23
23
  const [zoomLevel, setZoomLevel] = React.useState(DEFAULT_ZOOM);
24
24
  const [transformOrigin, setTransformOrigin] = React.useState(DEFAULT_ORIGIN);
25
- React.useEffect(() => {
25
+ const [zoomedContent, setZoomedContent] = React.useState(fileContent);
26
+ if (fileContent !== zoomedContent) {
27
+ setZoomedContent(fileContent);
26
28
  setZoomLevel(DEFAULT_ZOOM);
27
29
  setTransformOrigin(DEFAULT_ORIGIN);
28
- }, [fileContent]);
30
+ }
29
31
  const getTransformOrigin = (event) => {
30
32
  const rect = event.currentTarget.getBoundingClientRect();
31
33
  if (!rect.width || !rect.height) {
@@ -7,7 +7,7 @@ import type { ReactNode } from 'react';
7
7
  */
8
8
  export declare function SettingsSectionHeader({ title, description, action, className, }: {
9
9
  title: string;
10
- description?: string;
10
+ description?: ReactNode;
11
11
  action?: ReactNode;
12
12
  className?: string;
13
13
  }): import("react").JSX.Element;
@@ -59,7 +59,7 @@ export const useAutosizeTextArea = ({ textAreaRef, triggerAutoSize, maxHeight =
59
59
  clearTimeout(resizeTimeoutRef.current);
60
60
  }
61
61
  };
62
- }, [textAreaRef.current, triggerAutoSize, minHeight, maxHeight, currentHeight, init]);
62
+ }, [textAreaRef, triggerAutoSize, minHeight, maxHeight, currentHeight, init]);
63
63
  };
64
64
  export const Textarea = React.forwardRef(({ maxHeight = Number.MAX_SAFE_INTEGER, minHeight = 52, className, onChange, value, variant: _variant, ...props }, ref) => {
65
65
  const textAreaRef = React.useRef(null);
@@ -19,25 +19,23 @@ import { shouldTypeOnChange, TYPE_SPEED_MS } from './typed-title-logic.js';
19
19
  import { TypingAnimation } from './typing-animation.js';
20
20
  import { cn } from '../lib/utils.js';
21
21
  import { useReducedMotion } from 'motion/react';
22
- import { useCallback, useEffect, useRef, useState } from 'react';
22
+ import { useCallback, useState } from 'react';
23
23
  export { TYPE_SPEED_MS };
24
24
  export function useTypeOnChange(text) {
25
- // The last value this row DISPLAYED, in a ref rather than state: it must not
26
- // itself cause a render, and it has to update in the same pass that decides
27
- // to animate, or a second change arriving mid-type compares against a stale
28
- // value and is missed.
29
- const previousRef = useRef(null);
25
+ // The last value this row DISPLAYED. It updates in the same render pass that
26
+ // decides to animate, or a second change arriving mid-type compares against
27
+ // a stale value and is missed.
28
+ const [previous, setPrevious] = useState(null);
30
29
  const [isTyping, setIsTyping] = useState(false);
31
30
  const [runId, setRunId] = useState(0);
32
31
  const reduceMotion = !!useReducedMotion();
33
- useEffect(() => {
34
- const previous = previousRef.current;
35
- previousRef.current = text;
36
- if (!shouldTypeOnChange({ previous, next: text, reduceMotion }))
37
- return;
38
- setIsTyping(true);
39
- setRunId((id) => id + 1);
40
- }, [text, reduceMotion]);
32
+ if (previous !== text) {
33
+ setPrevious(text);
34
+ if (shouldTypeOnChange({ previous, next: text, reduceMotion })) {
35
+ setIsTyping(true);
36
+ setRunId((id) => id + 1);
37
+ }
38
+ }
41
39
  const stop = useCallback(() => setIsTyping(false), []);
42
40
  return { isTyping, runId, stop };
43
41
  }
@@ -34,12 +34,14 @@ export function TypingAnimation({ children, words, className, duration = 100, ty
34
34
  const deletingSpeed = deleteSpeed ?? typingSpeed / 2;
35
35
  const shouldStart = startOnView ? isInView : true;
36
36
  const animationSourceKey = useMemo(() => (words ? words.join('\u0000') : (children ?? '')), [words, children]);
37
- useEffect(() => {
37
+ const [seenSourceKey, setSeenSourceKey] = useState(animationSourceKey);
38
+ if (animationSourceKey !== seenSourceKey) {
39
+ setSeenSourceKey(animationSourceKey);
38
40
  setDisplayedText('');
39
41
  setCurrentWordIndex(0);
40
42
  setCurrentCharIndex(0);
41
43
  setPhase('typing');
42
- }, [animationSourceKey]);
44
+ }
43
45
  useEffect(() => {
44
46
  let timeout = null;
45
47
  if (shouldStart && wordsToAnimate.length > 0) {
@@ -45,7 +45,12 @@ export function AgentSelector({ agents, selectedAgent, onSelect, disabled = fals
45
45
  const [search, setSearch] = useState('');
46
46
  const { offeredAgents, agentLabels } = useUiHostConfig();
47
47
  // A host that lists its agents (`offeredAgents`) offers only those; it names them with `agentLabels`.
48
- const offered = useMemo(() => (offeredAgents ? agents.filter((a) => offeredAgents.includes(a.name)) : agents), [agents, offeredAgents]);
48
+ const offered = useMemo(() => {
49
+ if (!offeredAgents)
50
+ return agents;
51
+ const offeredNames = new Set(offeredAgents);
52
+ return agents.filter((a) => offeredNames.has(a.name));
53
+ }, [agents, offeredAgents]);
49
54
  const primaryAgents = useMemo(() => composerSelectableAgents(offered), [offered]);
50
55
  const label = (name) => agentLabels?.[name] ?? (isMetaAgentName(name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(name));
51
56
  // A `flash` state, a `prevAgentRef` feeding it, two effects and a 400ms
@@ -51,8 +51,6 @@ export declare function splitFilenameForTile(name: string): {
51
51
  head: string;
52
52
  tail: string;
53
53
  } | null;
54
- /** The one-string form, for places that cannot render two lines (tooltips, tests). */
55
- export declare function middleTruncateFilename(name: string, max?: number): string;
56
54
  /** A tile previews the head of the paste only: the clamp shows four lines, and the DOM need not hold 100 kB. */
57
55
  export declare const PASTE_PREVIEW_CHARS = 400;
58
56
  export interface AttachmentTileProps {
@@ -58,14 +58,6 @@ export function splitFilenameForTile(name) {
58
58
  return null;
59
59
  return { head: name.slice(0, -NAME_TAIL_CHARS), tail: name.slice(-NAME_TAIL_CHARS) };
60
60
  }
61
- /** The one-string form, for places that cannot render two lines (tooltips, tests). */
62
- export function middleTruncateFilename(name, max = 22) {
63
- if (name.length <= max)
64
- return name;
65
- const tail = Math.min(NAME_TAIL_CHARS, Math.floor((max - 1) / 2));
66
- const head = max - 1 - tail;
67
- return `${name.slice(0, head)}…${name.slice(-tail)}`;
68
- }
69
61
  /** A tile previews the head of the paste only: the clamp shows four lines, and the DOM need not hold 100 kB. */
70
62
  export const PASTE_PREVIEW_CHARS = 400;
71
63
  /**
@@ -2,7 +2,7 @@ export interface ClipboardItemLike {
2
2
  readonly kind: string;
3
3
  getAsFile(): File | null;
4
4
  }
5
- export interface ClipboardPayload {
5
+ interface ClipboardPayload {
6
6
  readonly files: ArrayLike<File>;
7
7
  readonly items: ArrayLike<ClipboardItemLike>;
8
8
  }
@@ -14,3 +14,4 @@ export interface ClipboardPayload {
14
14
  * handle the text itself.
15
15
  */
16
16
  export declare function extractClipboardFiles(data: ClipboardPayload | null | undefined): File[];
17
+ export {};