@kortix/ui 0.14.4 → 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 (228) hide show
  1. package/README.md +8 -4
  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-grant-pages.js +5 -6
  12. package/dist/customize/agents/agent-page.js +2 -1
  13. package/dist/customize/agents/agents-page.js +18 -1
  14. package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
  15. package/dist/customize/connectors/connector-routes.d.ts +3 -1
  16. package/dist/customize/connectors/connector-routes.js +18 -0
  17. package/dist/customize/connectors/connectors-page.js +23 -4
  18. package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
  19. package/dist/customize/providers/provider-accordion.js +2 -1
  20. package/dist/customize/providers/provider-connect.js +2 -1
  21. package/dist/customize/sections/component/telegram-channel-row.js +1 -1
  22. package/dist/customize/sections/connector-connection-modal.js +11 -9
  23. package/dist/customize/sections/connectors-view.js +11 -13
  24. package/dist/customize/sections/llm-provider/constants.d.ts +0 -1
  25. package/dist/customize/sections/llm-provider/constants.js +0 -2
  26. package/dist/customize/sections/llm-provider/models-tab-group.d.ts +1 -1
  27. package/dist/customize/sections/llm-provider/models-tab-group.js +7 -4
  28. package/dist/customize/sections/llm-provider/models-tab-row.d.ts +2 -18
  29. package/dist/customize/sections/llm-provider/models-tab-row.js +6 -30
  30. package/dist/customize/sections/llm-provider/models-tab.d.ts +0 -1
  31. package/dist/customize/sections/llm-provider/models-tab.js +1 -10
  32. package/dist/customize/sections/llm-provider/use-connected-providers.js +2 -2
  33. package/dist/customize/sections/llm-provider/utils.d.ts +1 -1
  34. package/dist/customize/sections/llm-provider/utils.js +7 -14
  35. package/dist/customize/sections/view/gateway/_shared.js +1 -2
  36. package/dist/customize/sections/view/gateway/gateway-routing.js +18 -20
  37. package/dist/customize/sections/view/sandbox-provider-coverage.d.ts +3 -18
  38. package/dist/customize/sections/view/sandbox-provider-coverage.js +7 -27
  39. package/dist/customize/settings/rail-copy-context.js +5 -3
  40. package/dist/customize/settings/tabs/sandbox-tab.d.ts +2 -27
  41. package/dist/customize/settings/tabs/sandbox-tab.js +13 -112
  42. package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
  43. package/dist/customize/shared/capability-tabs.js +2 -1
  44. package/dist/customize/shared/entity/entity-modal.d.ts +5 -1
  45. package/dist/customize/shared/entity/entity-modal.js +7 -4
  46. package/dist/customize/skills/remove-skill-button.d.ts +16 -0
  47. package/dist/customize/skills/remove-skill-button.js +42 -0
  48. package/dist/customize/skills/skills-page.js +4 -6
  49. package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
  50. package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
  51. package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
  52. package/dist/customize/triggers/schedule/trigger-list.d.ts +0 -2
  53. package/dist/customize/triggers/schedule/trigger-list.js +1 -18
  54. package/dist/customize/triggers/schedule/trigger-tile.d.ts +1 -32
  55. package/dist/customize/triggers/schedule/trigger-tile.js +1 -16
  56. package/dist/customize/triggers/schedule/when-event.js +1 -1
  57. package/dist/diff/diff-view.js +9 -10
  58. package/dist/files/editor/code-editor.js +2 -3
  59. package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
  60. package/dist/files/project/components/change-request-detail-dialog.js +3 -5
  61. package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
  62. package/dist/files/project/components/change-request-status-badge.js +27 -0
  63. package/dist/files/project/components/change-requests-panel.js +1 -1
  64. package/dist/files/project/components/drive-explorer.js +2 -1
  65. package/dist/files/project/components/drive-grid-view.js +11 -7
  66. package/dist/files/project/components/drive-list-view.js +4 -4
  67. package/dist/files/project/components/file-breadcrumbs.js +1 -1
  68. package/dist/files/project/components/file-explorer-page.js +1 -1
  69. package/dist/files/project/components/file-history-popover.js +1 -6
  70. package/dist/files/project/components/file-search.js +6 -1
  71. package/dist/files/project/components/open-change-request-dialog.js +2 -1
  72. package/dist/files/project/components/version-selector-logic.d.ts +11 -0
  73. package/dist/files/project/components/version-selector-logic.js +26 -0
  74. package/dist/files/project/components/version-selector.js +11 -20
  75. package/dist/files/project/hooks/use-change-requests.d.ts +14 -1
  76. package/dist/files/project/hooks/use-change-requests.js +44 -25
  77. package/dist/files/project/hooks/use-server-health.js +17 -15
  78. package/dist/files/renderers/docx/docx-viewer.js +4 -6
  79. package/dist/files/renderers/image-renderer.js +6 -24
  80. package/dist/files/renderers/pdf/pdf-renderer.d.ts +3 -1
  81. package/dist/files/renderers/pdf/pdf-renderer.js +2 -2
  82. package/dist/files/renderers/pdf/pdf-viewer.d.ts +3 -0
  83. package/dist/files/renderers/pdf/pdf-viewer.js +16 -7
  84. package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
  85. package/dist/files/renderers/show-content-renderer.js +1 -1
  86. package/dist/files/renderers/sqlite-renderer.js +8 -3
  87. package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
  88. package/dist/files/tree/components/file-tree-item.js +1 -1
  89. package/dist/files/viewer/file-content-renderer.d.ts +4 -1
  90. package/dist/files/viewer/file-content-renderer.js +2 -2
  91. package/dist/files/viewer/file-preview-modal.js +1 -1
  92. package/dist/files/viewer/use-file-content-state.js +1 -1
  93. package/dist/i18n/en.d.ts +36 -35
  94. package/dist/i18n/en.js +45 -44
  95. package/dist/index.d.ts +1 -1
  96. package/dist/internal/desktop/desktop-back-button.js +5 -3
  97. package/dist/internal/hooks/use-auto-scroll.js +5 -3
  98. package/dist/internal/hooks/use-notification-center.js +21 -15
  99. package/dist/internal/hooks/use-review-session-summary.d.ts +6 -0
  100. package/dist/internal/hooks/use-review-session-summary.js +11 -0
  101. package/dist/internal/lib/browser-error-noise.d.ts +1 -1
  102. package/dist/internal/lib/browser-error-noise.js +1 -1
  103. package/dist/internal/lib/browser-noise/rules/rejections.d.ts +10 -0
  104. package/dist/internal/lib/browser-noise/rules/rejections.js +27 -1
  105. package/dist/internal/lib/provider-branding.js +14 -3
  106. package/dist/internal/lib/trigger-clock.d.ts +42 -0
  107. package/dist/internal/lib/trigger-clock.js +34 -0
  108. package/dist/internal/lib/web-notifications.d.ts +6 -0
  109. package/dist/internal/lib/web-notifications.js +57 -7
  110. package/dist/internal/notifications/notification-bell.d.ts +13 -0
  111. package/dist/internal/notifications/notification-bell.js +1 -1
  112. package/dist/internal/notifications/notification-rows.js +2 -1
  113. package/dist/internal/outcomes/outcome-types.d.ts +5 -0
  114. package/dist/internal/reminders/reminder-filter.d.ts +12 -0
  115. package/dist/internal/reminders/reminder-filter.js +34 -0
  116. package/dist/internal/reminders/reminder-format.d.ts +6 -0
  117. package/dist/internal/reminders/reminder-format.js +20 -2
  118. package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
  119. package/dist/internal/stores/session-hover-store.d.ts +20 -5
  120. package/dist/internal/stores/session-hover-store.js +31 -13
  121. package/dist/internal/stores/session-panel-memory-store.js +2 -0
  122. package/dist/markdown/code/inline-code.js +8 -0
  123. package/dist/markdown/code/output-paths-context.d.ts +18 -0
  124. package/dist/markdown/code/output-paths-context.js +35 -0
  125. package/dist/markdown/genui/components/charts.js +6 -5
  126. package/dist/primitives/count-badge.d.ts +3 -1
  127. package/dist/primitives/count-badge.js +3 -3
  128. package/dist/primitives/glyph-paths-source.js +1 -1
  129. package/dist/primitives/glyph-paths.generated.js +1 -1
  130. package/dist/primitives/map.js +2 -1
  131. package/dist/primitives/preview-image.js +4 -2
  132. package/dist/primitives/textarea.js +1 -1
  133. package/dist/primitives/typed-title.js +12 -14
  134. package/dist/primitives/typing-animation.js +4 -2
  135. package/dist/providers/ui-provider.d.ts +57 -0
  136. package/dist/providers/ui-provider.js +4 -0
  137. package/dist/session/composer/ComposerCard.d.ts +2 -0
  138. package/dist/session/composer/ComposerCard.js +5 -1
  139. package/dist/session/composer/agent-selector.js +17 -5
  140. package/dist/session/composer/composer-chat-input.js +5 -3
  141. package/dist/session/composer/composer-logic.js +8 -7
  142. package/dist/session/composer/composer-toolbar.js +4 -2
  143. package/dist/session/composer/composer-underbar.js +3 -1
  144. package/dist/session/composer/composer.js +34 -5
  145. package/dist/session/composer/draft/composer-draft.js +4 -4
  146. package/dist/session/composer/menus/slash-actions.d.ts +22 -2
  147. package/dist/session/composer/menus/slash-actions.js +20 -0
  148. package/dist/session/composer/menus/slash-items.js +19 -3
  149. package/dist/session/composer/pasted-text.js +3 -2
  150. package/dist/session/composer/stores/queued-draft-store.js +3 -2
  151. package/dist/session/header/reminder-parts.d.ts +294 -0
  152. package/dist/session/header/reminder-parts.js +120 -0
  153. package/dist/session/header/session-reminders-indicator.d.ts +13 -0
  154. package/dist/session/header/session-reminders-indicator.js +142 -35
  155. package/dist/session/media/sandbox-url-detector.js +0 -7
  156. package/dist/session/models/model-availability.d.ts +23 -0
  157. package/dist/session/models/model-availability.js +20 -0
  158. package/dist/session/models/model-grouping.js +1 -4
  159. package/dist/session/models/model-selector.js +23 -14
  160. package/dist/session/models/use-model-connection-gate.js +4 -1
  161. package/dist/session/outcomes/change-request-outcomes.js +24 -3
  162. package/dist/session/outcomes/session-outcomes-provider.d.ts +3 -1
  163. package/dist/session/outcomes/session-outcomes-provider.js +31 -3
  164. package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
  165. package/dist/session/overrides/session-overrides-composer.d.ts +1 -1
  166. package/dist/session/overrides/session-overrides-composer.js +5 -1
  167. package/dist/session/panel/advanced/advanced-panel.js +3 -7
  168. package/dist/session/panel/easy/app-preview.js +12 -2
  169. package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
  170. package/dist/session/panel/easy/easy-panel-logic.js +1 -1
  171. package/dist/session/panel/easy/outputs-card.js +2 -1
  172. package/dist/session/panel/easy/panel-card.js +5 -3
  173. package/dist/session/panel/easy/preview-load.d.ts +14 -0
  174. package/dist/session/panel/easy/preview-load.js +15 -0
  175. package/dist/session/panel/easy/step-detail-body.js +2 -7
  176. package/dist/session/panel/mobile-tool-drawer.js +4 -2
  177. package/dist/session/panel/open-session-quick-view.js +2 -1
  178. package/dist/session/panel/panel-fallback.js +8 -4
  179. package/dist/session/panel/session-panel-provider.d.ts +1 -0
  180. package/dist/session/panel/session-panel-provider.js +19 -3
  181. package/dist/session/panel/session-reminders-panel.d.ts +4 -0
  182. package/dist/session/panel/session-reminders-panel.js +109 -0
  183. package/dist/session/panel/shared/use-panel-derivations.js +6 -4
  184. package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
  185. package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
  186. package/dist/session/panel/use-session-panel-layout.js +7 -5
  187. package/dist/session/print/session-print-header.d.ts +0 -10
  188. package/dist/session/print/session-print-header.js +25 -9
  189. package/dist/session/status/session-surface.js +1 -1
  190. package/dist/session/thread/optimistic-turn.d.ts +5 -1
  191. package/dist/session/thread/optimistic-turn.js +8 -6
  192. package/dist/session/thread/session-chat.d.ts +6 -1
  193. package/dist/session/thread/session-chat.js +34 -12
  194. package/dist/session/thread/session-history.js +3 -1
  195. package/dist/session/thread/session-thread.d.ts +10 -1
  196. package/dist/session/thread/session-thread.js +2 -2
  197. package/dist/session/thread/transcript.js +13 -7
  198. package/dist/session/tool/shared/file-list.js +19 -2
  199. package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
  200. package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
  201. package/dist/session/tool/shared/infrastructure-output.js +9 -5
  202. package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
  203. package/dist/session/tool/shared/infrastructure-rows.js +7 -5
  204. package/dist/session/tool/tools/bash-tool.js +10 -6
  205. package/dist/session/tool/tools/web-search-tool.js +25 -3
  206. package/dist/session/turn/burst-step-body.js +4 -0
  207. package/dist/session/turn/recovered-failures.d.ts +8 -0
  208. package/dist/session/turn/recovered-failures.js +32 -0
  209. package/dist/session/turn/streaming-cadence.js +5 -8
  210. package/dist/styles.css +1 -1
  211. package/dist/terminal/session-terminal-panel.js +3 -5
  212. package/dist/theme/theme.js +2 -1
  213. package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
  214. package/dist/workspace/review/review-center.js +13 -10
  215. package/dist/workspace/session-workspace.js +5 -9
  216. package/dist/workspace/sessions/deleted-session-row.js +5 -2
  217. package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
  218. package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
  219. package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
  220. package/dist/workspace/sidebar/project-session-list.js +52 -55
  221. package/dist/workspace/sidebar/session-brief-hover-card.d.ts +35 -18
  222. package/dist/workspace/sidebar/session-brief-hover-card.js +114 -37
  223. package/dist/workspace/sidebar/session-status-mark.d.ts +15 -1
  224. package/dist/workspace/sidebar/session-status-mark.js +63 -17
  225. package/package.json +7 -3
  226. package/skills/kortix-ui/SKILL.md +1 -1
  227. package/dist/customize/sections/view/sandbox-provider-result.d.ts +0 -47
  228. package/dist/customize/sections/view/sandbox-provider-result.js +0 -79
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useMemo } from 'react';
4
4
  import { SessionDrivesWatch } from '../../files/drives/session-drives-chip.js';
5
5
  import { useProjectSession } from '@kortix/sdk/react';
6
+ import { useUiHostConfig } from '../../providers/ui-provider.js';
6
7
  import { ProviderPoolDraftBoundary } from './provider-pool-draft-context.js';
7
8
  import { SessionOverridesToolbar } from './session-overrides-toolbar.js';
8
9
  /**
@@ -28,10 +29,13 @@ import { SessionOverridesToolbar } from './session-overrides-toolbar.js';
28
29
  * `session-chat.tsx`) build the panel from one place.
29
30
  */
30
31
  export function SessionOverridesComposer({ projectId, sessionId, onCommittedDraft, providerSecretPools, onProviderSecretPoolsChange, selectedAgent, sandboxSlot, }) {
31
- const sessionRow = useProjectSession(projectId, sessionId, { enabled: Boolean(sessionId) });
32
+ const { sessionOverridesEnabled } = useUiHostConfig();
33
+ const sessionRow = useProjectSession(projectId, sessionId, { enabled: Boolean(sessionId) && sessionOverridesEnabled });
32
34
  const sandbox = useMemo(() => ({
33
35
  slug: sessionRow.data?.metadata?.sandbox_slug ?? null,
34
36
  provider: sessionRow.data?.sandbox_provider ?? null,
35
37
  }), [sessionRow.data?.metadata, sessionRow.data?.sandbox_provider]);
38
+ if (!sessionOverridesEnabled)
39
+ return null;
36
40
  return (_jsxs(ProviderPoolDraftBoundary, { identity: `${projectId}/${sessionId ?? 'new'}`, children: [sessionId ? _jsx(SessionDrivesWatch, { projectId: projectId, sessionId: sessionId }) : null, _jsx(SessionOverridesToolbar, { projectId: projectId, sessionId: sessionId, agentName: selectedAgent ?? undefined, onCommittedDraft: onCommittedDraft, providerSecretPools: providerSecretPools, onProviderSecretPoolsChange: onProviderSecretPoolsChange, sandbox: sandbox, sandboxSlot: sandboxSlot }, `${projectId}/${sessionId ?? 'new'}`)] }));
37
41
  }
@@ -24,13 +24,9 @@ export const AdvancedPanel = memo(function AdvancedPanel({ sessionId, messages,
24
24
  // 'live' follows the latest tool as new ones stream in; 'manual' pins the
25
25
  // user's chosen index until they navigate back to the latest.
26
26
  const [mode, setMode] = useState('live');
27
- // Live-follow + clamp when the list grows/shrinks.
28
- useEffect(() => {
29
- if (count === 0)
30
- return;
31
- setIndex((i) => (mode === 'live' ? count - 1 : Math.min(i, count - 1)));
32
- }, [count, mode]);
33
- const safeIndex = Math.min(index, Math.max(0, count - 1));
27
+ // Live-follow + clamp when the list grows/shrinks, derived in render so a
28
+ // streamed call never shows the previous one for a frame.
29
+ const safeIndex = Math.max(0, Math.min(mode === 'live' ? count - 1 : index, count - 1));
34
30
  const current = calls[safeIndex];
35
31
  const atLatest = safeIndex >= count - 1;
36
32
  const isLive = atLatest && mode === 'live';
@@ -40,7 +40,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore
40
40
  import { PreviewLoadingOverlay, SandboxAddressBar, } from '../../../browser/sandbox-browser-chrome.js';
41
41
  import { BrowserLanding } from '../../../browser/browser-landing.js';
42
42
  import { CloseButton, DetailSidebarToggle } from './detail-view.js';
43
- import { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, sandboxRecents, shouldArmLoadTimeout, shouldKeepProbingPort, } from './easy-panel-logic.js';
43
+ import { PREVIEW_MAX_WAIT_MS, PREVIEW_SLOW_MS, PREVIEW_PROBE_INTERVAL_MS, previewErrorReason, previewSlowReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, sandboxRecents, shouldArmLoadTimeout, shouldKeepProbingPort, } from './easy-panel-logic.js';
44
44
  import { PanelWidthButton, ViewerActions } from '../../../files/viewer/viewer-actions.js';
45
45
  // zustand v5's own hook feeds React's `useSyncExternalStore` a
46
46
  // `getServerSnapshot` pinned to `getInitialState()` — correct for real SSR
@@ -94,6 +94,8 @@ export function AppPreview({ url, name, projectId, shareContext, onClose, onSend
94
94
  const [refreshKey, setRefreshKey] = useState(0);
95
95
  const [isLoading, setIsLoading] = useState(true);
96
96
  const [hasError, setHasError] = useState(false);
97
+ // Still loading past PREVIEW_SLOW_MS: the overlay says so and offers Reload.
98
+ const [slow, setSlow] = useState(false);
97
99
  const addressRef = useRef(null);
98
100
  const isMobile = useIsMobile();
99
101
  // Copy link mints a PUBLIC share and copies `/share/session/{token}`.
@@ -180,13 +182,21 @@ export function AppPreview({ url, name, projectId, shareContext, onClose, onSend
180
182
  if (alive)
181
183
  decide('unknown', PREVIEW_MAX_WAIT_MS);
182
184
  }, PREVIEW_MAX_WAIT_MS);
185
+ // A stalled request fires no onLoad and the probe cannot see it: say so
186
+ // instead of spinning silently until the deadline (KRTX-3015).
187
+ const slowTimer = setTimeout(() => {
188
+ if (alive)
189
+ setSlow(true);
190
+ }, PREVIEW_SLOW_MS);
183
191
  void tick();
184
192
  return () => {
185
193
  alive = false;
186
194
  controller.abort();
187
195
  clearTimeout(deadline);
196
+ clearTimeout(slowTimer);
188
197
  if (timer)
189
198
  clearTimeout(timer);
199
+ setSlow(false);
190
200
  };
191
201
  }, [isLoading, refreshKey, noApp, hasPreview, previewUrl]);
192
202
  // Nothing to load, so land the cursor on the address bar — the fastest way
@@ -234,7 +244,7 @@ export function AppPreview({ url, name, projectId, shareContext, onClose, onSend
234
244
  return;
235
245
  track('app_opened_new_tab');
236
246
  window.open(previewUrl, '_blank', 'noopener,noreferrer');
237
- }, className: "size-7 shrink-0 active:scale-[0.96]", children: _jsx(ArrowSquareOutIcon, { className: "size-3.5" }) }) }), _jsx(PanelWidthButton, { isMobile: isMobile }), _jsx(CloseButton, { onClose: onClose })] })] }), _jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: [isLoading && hasPreview && !noApp && (_jsx(PreviewLoadingOverlay, { label: tI18nComplete.raw('textc4cf2b2ccb5d') })), (hasError || hostPreview.failed) && !noApp && (_jsx(ErrorState, { icon: WarningIcon, size: "sm", className: "bg-background absolute inset-0 z-10", title: tI18nComplete('textba601cdb484b', { value0: title }),
247
+ }, className: "size-7 shrink-0 active:scale-[0.96]", children: _jsx(ArrowSquareOutIcon, { className: "size-3.5" }) }) }), _jsx(PanelWidthButton, { isMobile: isMobile }), _jsx(CloseButton, { onClose: onClose })] })] }), _jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: [isLoading && hasPreview && !noApp && (_jsx(PreviewLoadingOverlay, { label: tI18nComplete.raw('textc4cf2b2ccb5d'), detail: slow ? previewSlowReason({ port }) : undefined, action: slow ? (_jsx(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: reload, children: tI18nComplete.raw('textbdc090ec61e3') })) : undefined })), (hasError || hostPreview.failed) && !noApp && (_jsx(ErrorState, { icon: WarningIcon, size: "sm", className: "bg-background absolute inset-0 z-10", title: tI18nComplete('textba601cdb484b', { value0: title }),
238
248
  /* The single most common cause, said plainly: the agent started the
239
249
  server a moment ago and it isn't listening yet. Only a SETTLED
240
250
  `dead` verdict earns the stopped-workspace wording — see
@@ -183,5 +183,5 @@ export declare function shouldAutoOpenPayoff(args: {
183
183
  interactedThisRun: boolean;
184
184
  }): boolean;
185
185
  export { aspectChangedWidth, fitSplitPercent, resolveSideSize } from '../session-panel-sizing.js';
186
- export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
186
+ export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_SLOW_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, previewSlowReason, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
187
187
  export type { PreviewProbe, SandboxHealth } from './preview-load.js';
@@ -238,4 +238,4 @@ export function shouldAutoOpenPayoff(args) {
238
238
  !args.interactedThisRun);
239
239
  }
240
240
  export { aspectChangedWidth, fitSplitPercent, resolveSideSize } from '../session-panel-sizing.js';
241
- export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
241
+ export { PREVIEW_MAX_WAIT_MS, PREVIEW_PROBE_INTERVAL_MS, PREVIEW_SLOW_MS, PREVIEW_UNREACHABLE_WINDOW_MS, previewErrorReason, previewLoadSuccessState, previewLoadVerdict, previewSlowReason, runtimeSandboxHealth, shouldArmLoadTimeout, shouldKeepProbingPort } from './preview-load.js';
@@ -145,7 +145,8 @@ export function OutputRows({ outputs, onOpenOutput, initialShowAll, }) {
145
145
  const deliverables = outputs.filter((o) => !isScaffoldingOutput(o));
146
146
  const base = deliverables.length > 0 ? deliverables : outputs;
147
147
  const visible = showAll ? outputs : base.slice(0, VISIBLE_LIMIT);
148
- const hiddenRows = outputs.filter((o) => !visible.includes(o));
148
+ const visibleSet = new Set(visible);
149
+ const hiddenRows = outputs.filter((o) => !visibleSet.has(o));
149
150
  const hidden = hiddenRows.length;
150
151
  const hiddenNoun = `${hiddenRows.every((o) => o.kind === 'file') ? 'file' : 'item'}${hidden === 1 ? '' : 's'}`;
151
152
  // Grouping only ever organizes the expanded long list (see
@@ -18,7 +18,7 @@ import { Empty, EmptyContent, EmptyDescription, EmptyMedia } from '../../../prim
18
18
  import { cn } from '../../../lib/utils.js';
19
19
  import { CaretRightIcon as ChevronRight } from '@phosphor-icons/react';
20
20
  import { m, useReducedMotion } from 'motion/react';
21
- import { useEffect, useState } from 'react';
21
+ import { useState } from 'react';
22
22
  /** Full-width row trigger, clipped by the parent's `overflow-hidden` so its square corners never peek past the card's rounded-md border. */
23
23
  const HEADER_CLASS = cn('flex min-h-11 w-full items-center justify-between gap-2 rounded-none px-3.5 py-2 text-left', 'transition-[background-color,transform] active:scale-[0.998]', 'hover:bg-muted-foreground/[0.04]');
24
24
  function CardTitleRow({ title, count, indicator, subtitle, chevron, }) {
@@ -31,10 +31,12 @@ export function PanelCard({ title, count, indicator, subtitle, children, emptyAr
31
31
  // `defaultExpanded` can flip true after mount (e.g. "auto-open Outputs the
32
32
  // moment a run finishes with something to show"). One-way sync: force open
33
33
  // when that happens, but never fight a user's manual collapse afterwards.
34
- useEffect(() => {
34
+ const [prevDefaultExpanded, setPrevDefaultExpanded] = useState(defaultExpanded);
35
+ if (defaultExpanded !== prevDefaultExpanded) {
36
+ setPrevDefaultExpanded(defaultExpanded);
35
37
  if (defaultExpanded)
36
38
  setExpanded(true);
37
- }, [defaultExpanded]);
39
+ }
38
40
  return (_jsxs(Disclosure, { open: expanded, onOpenChange: setExpanded, variant: "outline", className: cn('bg-pane text-popover-foreground border-border overflow-hidden rounded-[calc(var(--radius)-3px)] border ease-out', fill ? 'flex min-h-0 flex-col' : 'shrink-0'), transition: transition, children: [_jsx(DisclosureTrigger, { variant: "outline", children: _jsx("div", { className: cn(HEADER_CLASS, 'cursor-pointer'), children: _jsx(CardTitleRow, { title: title, count: count, indicator: indicator, subtitle: subtitle, chevron: _jsxs("span", { className: "flex shrink-0 items-center gap-0.5", children: [headerAction && (
39
41
  // Isolated from the toggle on both input paths: `stopPropagation`
40
42
  // on click covers the mouse/tap case, and on keydown stops the
@@ -91,6 +91,20 @@ export declare const PREVIEW_UNREACHABLE_WINDOW_MS = 10000;
91
91
  * 50% margin. It is a last resort, not the normal path — with a working probe
92
92
  * a dead port resolves in ~{@link PREVIEW_UNREACHABLE_WINDOW_MS}. */
93
93
  export declare const PREVIEW_MAX_WAIT_MS = 90000;
94
+ /** How long the frame may load before the pane says it is slow, ms.
95
+ *
96
+ * A frame whose request stalls (the app has not answered yet, or a
97
+ * single-threaded server is busy with another client) fires no `onLoad`, and
98
+ * the port probe cannot see that. The pane then showed a bare "Loading
99
+ * preview…" until {@link PREVIEW_MAX_WAIT_MS} (KRTX-3015). Past this point it
100
+ * keeps waiting but says so, and offers Reload. Fifteen seconds is past the
101
+ * probe's {@link PREVIEW_UNREACHABLE_WINDOW_MS}, so a dead port has already
102
+ * failed by then, and well inside a cold compile, which only earns a line. */
103
+ export declare const PREVIEW_SLOW_MS = 15000;
104
+ /** The line under the loading spinner once the preview is slow. */
105
+ export declare function previewSlowReason(input: {
106
+ port: number;
107
+ }): string;
94
108
  /** One probe's verdict about the port. Aliased from the SDK's own type rather
95
109
  * than restated, so the two can never drift apart. */
96
110
  export type PreviewProbe = PreviewPortProbe;
@@ -63,6 +63,21 @@ export const PREVIEW_UNREACHABLE_WINDOW_MS = 10_000;
63
63
  * 50% margin. It is a last resort, not the normal path — with a working probe
64
64
  * a dead port resolves in ~{@link PREVIEW_UNREACHABLE_WINDOW_MS}. */
65
65
  export const PREVIEW_MAX_WAIT_MS = 90_000;
66
+ /** How long the frame may load before the pane says it is slow, ms.
67
+ *
68
+ * A frame whose request stalls (the app has not answered yet, or a
69
+ * single-threaded server is busy with another client) fires no `onLoad`, and
70
+ * the port probe cannot see that. The pane then showed a bare "Loading
71
+ * preview…" until {@link PREVIEW_MAX_WAIT_MS} (KRTX-3015). Past this point it
72
+ * keeps waiting but says so, and offers Reload. Fifteen seconds is past the
73
+ * probe's {@link PREVIEW_UNREACHABLE_WINDOW_MS}, so a dead port has already
74
+ * failed by then, and well inside a cold compile, which only earns a line. */
75
+ export const PREVIEW_SLOW_MS = 15_000;
76
+ /** The line under the loading spinner once the preview is slow. */
77
+ export function previewSlowReason(input) {
78
+ const app = input.port > 0 ? `The app on port ${input.port}` : 'The app';
79
+ return `${app} is taking a while to answer. Reload to ask again.`;
80
+ }
66
81
  /**
67
82
  * Whether `AppPreview` should keep waiting or show "Couldn't load".
68
83
  *
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useState } from 'react';
3
+ import { useCallback, useState } from 'react';
4
4
  import { ActionNavigator } from '../shared/action-navigator.js';
5
5
  import { clampIndex } from '../shared/action-navigator-logic.js';
6
6
  import { ToolParts } from './detail-view.js';
@@ -15,12 +15,7 @@ focusCallId, }) {
15
15
  // Arriving from a chat click pins the view to that call; 'live' would drag
16
16
  // it straight back to the newest part on the next streaming update.
17
17
  const [mode, setMode] = useState(focusIndex >= 0 ? 'manual' : 'live');
18
- useEffect(() => {
19
- if (calls.length === 0)
20
- return;
21
- setIndex((i) => (mode === 'live' ? calls.length - 1 : clampIndex(i, calls.length)));
22
- }, [calls.length, mode]);
23
- const safeIndex = clampIndex(index, calls.length);
18
+ const safeIndex = clampIndex(mode === 'live' ? calls.length - 1 : index, calls.length);
24
19
  const current = calls[safeIndex];
25
20
  const atLatest = safeIndex >= calls.length - 1;
26
21
  // Stable identity so ActionNavigator's keydown effect doesn't re-subscribe
@@ -21,22 +21,24 @@ import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '../../primitiv
21
21
  import { BrowserPanel } from '../../browser/browser-panel.js';
22
22
  import { CloseButton } from './easy/detail-view.js';
23
23
  import { SessionAuditPanel } from './session-audit-panel.js';
24
+ import { SessionRemindersPanel } from './session-reminders-panel.js';
24
25
  import { SessionFilesExplorer } from './session-files-explorer.js';
25
26
  import { SessionTerminalPanel } from '../../terminal/session-terminal-panel.js';
26
27
  import { useLocalizedUiCatalog } from '../../i18n/ui-catalog/use-localized-ui-catalog.js';
27
28
  import { useKortixComputerStore } from '../../internal/stores/kortix-computer-store.js';
28
29
  import { sessionPreviewTabId } from '../../internal/stores/session-browser-store.js';
29
- import { FolderOpenIcon, GlobeIcon, ScrollIcon, TerminalWindowIcon, } from '@phosphor-icons/react';
30
+ import { AlarmIcon, FolderOpenIcon, GlobeIcon, ScrollIcon, TerminalWindowIcon, } from '@phosphor-icons/react';
30
31
  const TOOL_META = {
31
32
  terminal: { label: 'Terminal', Icon: TerminalWindowIcon },
32
33
  browser: { label: 'Browser', Icon: GlobeIcon },
33
34
  files: { label: 'Files', Icon: FolderOpenIcon },
34
35
  audit: { label: 'Audit', Icon: ScrollIcon },
36
+ reminders: { label: 'Reminders', Icon: AlarmIcon },
35
37
  };
36
38
  export function MobileToolDrawer({ sessionId, projectId, projectSessionId, }) {
37
39
  const view = useKortixComputerStore((s) => s.mobileToolView);
38
40
  const closeMobileTool = useKortixComputerStore((s) => s.closeMobileTool);
39
41
  const toolMeta = useLocalizedUiCatalog(TOOL_META);
40
42
  const meta = view ? toolMeta[view] : null;
41
- 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: [view === 'terminal' && (_jsx(SessionTerminalPanel, { sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), view === 'browser' && (_jsx(BrowserPanel, { tabId: sessionPreviewTabId(sessionId), projectId: projectId, projectSessionId: projectSessionId })), view === 'files' && (_jsx(SessionFilesExplorer, { chatSessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), view === 'audit' && (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId }))] })] }) }));
43
+ 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: [view === 'terminal' && (_jsx(SessionTerminalPanel, { sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), view === 'browser' && (_jsx(BrowserPanel, { tabId: sessionPreviewTabId(sessionId), projectId: projectId, projectSessionId: projectSessionId })), view === 'files' && (_jsx(SessionFilesExplorer, { chatSessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), 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 }) }))] })] }) }));
42
44
  }
@@ -55,7 +55,8 @@ target) {
55
55
  // is tab-gated — relying on the latter silently dropped the terminal open
56
56
  // on /projects/:id/sessions/:id.
57
57
  const activePanelSessionId = getActivePanelSessionId();
58
- if (panelMode === 'advanced') {
58
+ // Reminders is an Easy detail with no Advanced tab: it takes the request path in both modes.
59
+ if (panelMode === 'advanced' && view !== 'reminders') {
59
60
  if (activePanelSessionId) {
60
61
  // `SessionPanelView` calls the file explorer 'explorer'; its 'files'
61
62
  // member is the git-changes diff view. The quick-view vocabulary uses
@@ -26,6 +26,7 @@ const AppPreview = lazy(() => import('./easy/app-preview.js').then((m) => ({ def
26
26
  const SessionTerminalPanel = lazy(() => import('../../terminal/session-terminal-panel.js').then((m) => ({ default: m.SessionTerminalPanel })));
27
27
  const SessionFilesExplorer = lazy(() => import('./session-files-explorer.js').then((m) => ({ default: m.SessionFilesExplorer })));
28
28
  const SessionAuditPanel = lazy(() => import('./session-audit-panel.js').then((m) => ({ default: m.SessionAuditPanel })));
29
+ const SessionRemindersPanel = lazy(() => import('./session-reminders-panel.js').then((m) => ({ default: m.SessionRemindersPanel })));
29
30
  export function SessionPanelFallback({ projectId, projectSessionId, runtimeSessionId, }) {
30
31
  useEffect(() => registerPanelFallbackHost(), []);
31
32
  const panelMounted = useSessionBrowserStore((s) => s.activeSessionId !== null);
@@ -35,6 +36,7 @@ export function SessionPanelFallback({ projectId, projectSessionId, runtimeSessi
35
36
  }
36
37
  function PanelViewModal({ projectId, projectSessionId, runtimeSessionId }) {
37
38
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
39
+ const tReminders = useTranslations('reminders');
38
40
  const request = usePanelFallbackStore((s) => s.request);
39
41
  if (!request)
40
42
  return null;
@@ -45,10 +47,12 @@ function PanelViewModal({ projectId, projectSessionId, runtimeSessionId }) {
45
47
  ? 'Terminal'
46
48
  : view === 'audit'
47
49
  ? tI18nComplete.raw('textbb6aea287396')
48
- : target?.changes
49
- ? 'Changes'
50
- : 'Files';
51
- const body = view === 'browser' ? (_jsx(AppPreview, { url: target?.url ?? '', name: title, projectId: projectId, shareContext: { projectId, sessionId: projectSessionId }, onClose: closePanelFallbackView })) : view === 'audit' ? (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })) : !runtimeSessionId ? (_jsx(Centered, { children: _jsx(Loading, { className: "size-5" }) })) : view === 'terminal' ? (_jsx(SessionTerminalPanel, { sessionId: runtimeSessionId, projectId: projectId, projectSessionId: projectSessionId })) : (_jsx(SessionFilesExplorer, { chatSessionId: runtimeSessionId, projectId: projectId, projectSessionId: projectSessionId, ephemeral: true, initialMode: target?.changes ? 'changes' : 'files' }));
50
+ : view === 'reminders'
51
+ ? tReminders('title')
52
+ : target?.changes
53
+ ? 'Changes'
54
+ : 'Files';
55
+ const body = view === 'browser' ? (_jsx(AppPreview, { url: target?.url ?? '', name: title, projectId: projectId, shareContext: { projectId, sessionId: projectSessionId }, onClose: closePanelFallbackView })) : view === 'audit' ? (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })) : view === 'reminders' ? (_jsx("div", { className: "p-4", children: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }) })) : !runtimeSessionId ? (_jsx(Centered, { children: _jsx(Loading, { className: "size-5" }) })) : view === 'terminal' ? (_jsx(SessionTerminalPanel, { sessionId: runtimeSessionId, projectId: projectId, projectSessionId: projectSessionId })) : (_jsx(SessionFilesExplorer, { chatSessionId: runtimeSessionId, projectId: projectId, projectSessionId: projectSessionId, ephemeral: true, initialMode: target?.changes ? 'changes' : 'files' }));
52
56
  return (_jsx(Modal, { open: true, onOpenChange: (open) => !open && closePanelFallbackView(), children: _jsxs(ModalContent
53
57
  // The browser has its own close button in its toolbar.
54
58
  , {
@@ -28,6 +28,7 @@ export interface SessionPanelValue {
28
28
  }) => void;
29
29
  openFiles: (changes?: boolean) => void;
30
30
  openAudit: () => void;
31
+ openReminders: () => void;
31
32
  }
32
33
  /**
33
34
  * Exported so a test can inject a stub value and render the cards (or
@@ -27,8 +27,10 @@ import { useTranslations } from '../../i18n/index.js';
27
27
  * that would drop a live PTY WebSocket. See `PersistentLayer`.
28
28
  */
29
29
  import { SessionAuditPanel } from './session-audit-panel.js';
30
+ import { SessionRemindersPanel } from './session-reminders-panel.js';
30
31
  import { SessionFilesExplorer } from './session-files-explorer.js';
31
32
  import { useSandboxProxy } from '../../internal/hooks/use-sandbox-proxy.js';
33
+ import { OutputPathsContext } from '../../markdown/code/output-paths-context.js';
32
34
  import { useIsMobile } from '../../hooks/use-mobile.js';
33
35
  import { track } from '../../lib/telemetry.js';
34
36
  import { parseLocalhostUrl } from '@kortix/sdk';
@@ -71,6 +73,7 @@ export function useOptionalSessionPanel() {
71
73
  }
72
74
  export function SessionPanelProvider({ sessionId, isSessionBusy = false, projectId, projectSessionId, children, }) {
73
75
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
76
+ const tReminders = useTranslations('reminders');
74
77
  const { steps, outputs, context, apps, files, isRunning, outcome } = usePanelDerivations({
75
78
  sessionId, projectId, projectSessionId, isSessionBusy,
76
79
  });
@@ -356,7 +359,6 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
356
359
  });
357
360
  setPanelSplit(split);
358
361
  }, [
359
- sessionId,
360
362
  getServiceUrl,
361
363
  closeDetail,
362
364
  openDetail,
@@ -376,6 +378,14 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
376
378
  body: _jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId }),
377
379
  });
378
380
  }, [openDetail, projectId, projectSessionId, tI18nComplete]);
381
+ // A session with more reminders than the header popover holds opens them here.
382
+ const openReminders = useCallback(() => {
383
+ openDetail({
384
+ key: 'reminders',
385
+ title: tReminders('title'),
386
+ body: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }),
387
+ });
388
+ }, [openDetail, projectId, projectSessionId, tReminders]);
379
389
  /**
380
390
  * The opt-in File Explorer (Marko's ask). Never a default view and never a
381
391
  * tab — it opens only when asked for, exactly like Terminal and Audit, so
@@ -418,7 +428,7 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
418
428
  }, [apps, handleOpenOutput]);
419
429
  const { outputsDefaultOpen, pendingQuickView } = usePanelHandoffs({
420
430
  sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps,
421
- detail, handleOpenOutput, openDetail, openTerminal, openAudit, openBrowser, openFiles,
431
+ detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openBrowser, openFiles,
422
432
  });
423
433
  // The panel comes back with the session: what it showed is remembered while
424
434
  // it is open, and reopened on the way back while the session still runs.
@@ -526,6 +536,10 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
526
536
  detail,
527
537
  memoryKey,
528
538
  ]);
539
+ // Inline code that names an output by its bare filename opens it (KRTX-3023).
540
+ // Keyed by content, so a re-derivation with the same paths re-renders no text.
541
+ const outputPathsKey = files.flatMap((f) => (f.path ? [f.path] : [])).join('\n');
542
+ const outputPaths = useMemo(() => (outputPathsKey ? outputPathsKey.split('\n') : []), [outputPathsKey]);
529
543
  const value = useMemo(() => ({
530
544
  sessionId,
531
545
  projectId,
@@ -545,6 +559,7 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
545
559
  openBrowser,
546
560
  openFiles,
547
561
  openAudit,
562
+ openReminders,
548
563
  }), [
549
564
  sessionId,
550
565
  projectId,
@@ -564,6 +579,7 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
564
579
  openBrowser,
565
580
  openFiles,
566
581
  openAudit,
582
+ openReminders,
567
583
  ]);
568
- return _jsx(SessionPanelContext.Provider, { value: value, children: children });
584
+ return (_jsx(SessionPanelContext.Provider, { value: value, children: _jsx(OutputPathsContext.Provider, { value: outputPaths, children: children }) }));
569
585
  }
@@ -0,0 +1,4 @@
1
+ export declare function SessionRemindersPanel({ projectId, projectSessionId, }: {
2
+ projectId?: string;
3
+ projectSessionId?: string;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * The session's reminders as a side-panel view, for a session with more than
5
+ * the header popover holds (`REMINDERS_POPOVER_MAX`). It is the Triggers list
6
+ * (`customize/triggers/schedule/trigger-list.tsx`) for reminders: the search
7
+ * field and underline tabs from the Triggers page, a time filter by next run,
8
+ * and the same 56px rows — the clock that carries its health, the name over
9
+ * its schedule, the next run over the last run. A row opens its prompt, facts
10
+ * and actions in place.
11
+ */
12
+ import { Button } from '../../primitives/button.js';
13
+ import { EmptyState } from '../../primitives/empty-state.js';
14
+ import { ErrorState } from '../../primitives/error-state.js';
15
+ import { InputGroupSearch, InputGroupSearchClear, InputGroupSearchIcon, InputGroupSearchInput, } from '../../primitives/input-group.js';
16
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../primitives/select.js';
17
+ import { Skeleton } from '../../primitives/skeleton.js';
18
+ import { Tabs, TabsList, TabsTrigger } from '../../primitives/tabs.js';
19
+ import { REFLECTION_MASK } from '../../primitives/toast.js';
20
+ import { useDebounce } from '../../hooks/use-debounce.js';
21
+ import { remindersHref } from '../../internal/lib/capability-tab-routes.js';
22
+ import { useCapabilityAccess } from '../../internal/layout/project-settings-nav.js';
23
+ import { reminderTitle, scheduleLabel } from '../../internal/reminders/reminder-format.js';
24
+ import { matchesQuery, matchesStatus, matchesTime, REMINDER_STATUS_FILTERS, REMINDER_TIME_FILTERS, } from '../../internal/reminders/reminder-filter.js';
25
+ import { useNow } from '../../internal/reminders/use-refetch-after-fire.js';
26
+ import { cn } from '../../internal/lib/utils.js';
27
+ import { useLocale, useTranslations } from '../../i18n/index.js';
28
+ import { AlarmIcon, MagnifyingGlassIcon as SearchIcon } from '@phosphor-icons/react';
29
+ import { useState } from 'react';
30
+ import { CLOCK_TONE, lastRunLabel } from '../../internal/lib/trigger-clock.js';
31
+ import { ReminderActions, ReminderFacts, isFailedReminder, sortReminders, useReminderControls, } from '../header/reminder-parts.js';
32
+ const STATUS_LABEL_KEY = {
33
+ all: 'statusAll',
34
+ active: 'tabActive',
35
+ paused: 'tabPaused',
36
+ failed: 'calendarFailed',
37
+ };
38
+ const TIME_LABEL_KEY = {
39
+ any: 'timeAny',
40
+ hour: 'timeHour',
41
+ day: 'timeDay',
42
+ week: 'timeWeek',
43
+ later: 'timeLater',
44
+ };
45
+ /** The Triggers clock (`TriggerTile`) in a reminder's health, drawn with the alarm the header chip uses. */
46
+ function ReminderTile({ reminder }) {
47
+ const tone = isFailedReminder(reminder)
48
+ ? CLOCK_TONE.failed
49
+ : reminder.state !== 'active'
50
+ ? CLOCK_TONE.paused
51
+ : CLOCK_TONE.ok;
52
+ return (_jsxs("span", { "aria-hidden": true, className: "relative size-5 shrink-0", children: [_jsx("span", { className: "text-muted-foreground absolute inset-0 flex items-center justify-center [&_svg]:size-5", children: _jsx(AlarmIcon, {}) }), _jsx("span", { className: cn('absolute inset-0 flex items-center justify-center [&_svg]:size-5', tone.reflection), style: { maskImage: REFLECTION_MASK, WebkitMaskImage: REFLECTION_MASK }, children: _jsx(AlarmIcon, {}) }), _jsx("span", { className: "absolute -right-0.75 -bottom-0.75 flex size-3 items-center justify-center rounded-full bg-(--row-surface)", children: _jsx("span", { className: cn('size-1.5 rounded-full', tone.dot) }) })] }));
53
+ }
54
+ /** "in 25m", "in 8h", "in 3d": the next run in the last-run label's narrow style. */
55
+ function nextRunIn(iso, locale, now) {
56
+ const at = iso ? Date.parse(iso) : Number.NaN;
57
+ if (!Number.isFinite(at))
58
+ return '—';
59
+ const rtf = new Intl.RelativeTimeFormat(locale, { style: 'narrow', numeric: 'auto' });
60
+ const min = Math.max(0, Math.round((at - now) / 60_000));
61
+ if (min < 60)
62
+ return rtf.format(min, 'minute');
63
+ if (min < 24 * 60)
64
+ return rtf.format(Math.round(min / 60), 'hour');
65
+ return rtf.format(Math.round(min / (24 * 60)), 'day');
66
+ }
67
+ function ReminderListRow({ reminder, schedule, now, open, onToggle, controls, manageHref, }) {
68
+ const t = useTranslations('reminders');
69
+ const tTriggers = useTranslations('hardcodedUi.i18nComplete');
70
+ const locale = useLocale();
71
+ const active = reminder.state === 'active';
72
+ // The Triggers last-run label takes a trigger's shape.
73
+ const asTrigger = { last_fired_at: reminder.last_fired_at, event: null };
74
+ return (_jsxs("li", { className: cn('rounded-md',
75
+ // One variable paints the row and the clock's dot disc, so the cutout
76
+ // always matches: rest = card, hover = muted at 60% over card, open = muted.
77
+ 'bg-(--row-surface) [--row-surface:var(--card)] transition-[background-color] duration-(--duration-fast)', open
78
+ ? '[--row-surface:var(--muted)]'
79
+ : 'hover:[--row-surface:color-mix(in_oklab,var(--muted)_60%,var(--card))] has-[button:focus-visible]:[--row-surface:color-mix(in_oklab,var(--muted)_60%,var(--card))]'), children: [_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, "data-testid": "session-reminder-panel-row", className: "flex h-14 w-full items-center gap-3 rounded-md px-3 text-left outline-none", children: [_jsx("span", { className: "flex size-5 shrink-0 items-center justify-center", children: _jsx(ReminderTile, { reminder: reminder }) }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "text-foreground truncate text-sm leading-tight font-medium", children: reminderTitle(reminder) }), _jsx("span", { className: "text-muted-foreground truncate text-xs leading-tight", children: schedule })] }), _jsxs("span", { className: "flex shrink-0 flex-col items-end text-xs tabular-nums", children: [_jsx("span", { className: cn('whitespace-nowrap', active ? 'text-foreground' : 'text-muted-foreground'), children: active ? nextRunIn(reminder.next_fire_at, locale, now) : t('pausedLabel') }), _jsx("span", { className: "text-muted-foreground whitespace-nowrap", children: lastRunLabel(asTrigger, tTriggers, locale, now) })] })] }), open ? (
80
+ // Starts under the name: the row's px-3, the 20px clock slot, gap-3.
81
+ _jsxs("div", { className: "space-y-3 pr-3 pb-3 pl-11", children: [reminder.name ? (_jsx("p", { className: "text-muted-foreground text-xs text-pretty whitespace-pre-line", children: reminder.prompt })) : null, _jsx(ReminderFacts, { reminder: reminder, now: now }), _jsx(ReminderActions, { reminder: reminder, controls: controls, manageHref: manageHref, className: "-ml-1.5" })] })) : null] }));
82
+ }
83
+ export function SessionRemindersPanel({ projectId, projectSessionId, }) {
84
+ const t = useTranslations('reminders');
85
+ const now = useNow();
86
+ const { reminders, controls, confirm } = useReminderControls(projectId ?? null, projectSessionId);
87
+ const hasTab = useCapabilityAccess(projectId).tabs.includes('reminders');
88
+ const manageHref = hasTab && projectId ? `${remindersHref(projectId)}?session=${projectSessionId}` : null;
89
+ const [query, setQuery] = useState('');
90
+ // The input shows every keystroke; the list filters 200 ms after the last one.
91
+ const { debouncedValue: debouncedQuery } = useDebounce(query, 200);
92
+ const [status, setStatus] = useState('all');
93
+ const [time, setTime] = useState('any');
94
+ const [openId, setOpenId] = useState(null);
95
+ if (reminders.isPending) {
96
+ return (_jsx("div", { className: "space-y-1", children: Array.from({ length: 6 }, (_, index) => (_jsx(Skeleton, { className: "h-14 rounded-md" }, index))) }));
97
+ }
98
+ if (reminders.isError) {
99
+ return (_jsx(ErrorState, { size: "sm", title: t('loadFailed'), action: _jsx(Button, { variant: "outline", size: "sm", onClick: () => void reminders.refetch(), children: t('retry') }) }));
100
+ }
101
+ const schedule = (reminder) => scheduleLabel(reminder, (key, values) => t(key, values));
102
+ const live = sortReminders((reminders.data?.reminders ?? []).filter((reminder) => reminder.state !== 'done'));
103
+ const shown = live.filter((reminder) => matchesStatus(reminder, status) &&
104
+ matchesTime(reminder, time, now) &&
105
+ matchesQuery(reminder, debouncedQuery, schedule(reminder)));
106
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(SearchIcon, {}) }), _jsx(InputGroupSearchInput, { placeholder: t('searchPlaceholder'), value: query, onChange: (event) => setQuery(event.target.value), size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Tabs, { value: status, onValueChange: (next) => setStatus(next), className: "min-w-0 overflow-x-auto", children: _jsx(TabsList, { type: "underline", underlineSize: "md", size: "sm", className: "h-8 justify-start gap-5", children: REMINDER_STATUS_FILTERS.map((value) => (_jsx(TabsTrigger, { value: value, className: "flex-none px-0 text-xs font-medium", children: t(STATUS_LABEL_KEY[value]) }, value))) }) }), _jsxs(Select, { value: time, onValueChange: (next) => setTime(next), children: [_jsx(SelectTrigger, { variant: "transparent", size: "sm", "aria-label": t('timeFilterLabel'), className: "text-muted-foreground h-8 shrink-0 text-xs", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { align: "end", children: REMINDER_TIME_FILTERS.map((value) => (_jsx(SelectItem, { value: value, children: t(TIME_LABEL_KEY[value]) }, value))) })] })] }), shown.length > 0 ? (
107
+ // The Triggers list's resting panel: `bg-card`, border, `rounded-lg`.
108
+ _jsx("ul", { className: "bg-card border-border flex flex-col rounded-lg border p-1", children: shown.map((reminder) => (_jsx(ReminderListRow, { reminder: reminder, schedule: schedule(reminder), now: now, open: openId === reminder.id, onToggle: () => setOpenId((current) => (current === reminder.id ? null : reminder.id)), controls: controls, manageHref: manageHref }, reminder.id))) })) : (_jsx(EmptyState, { size: "sm", title: t('noMatch') })), shown.length < live.length ? (_jsx("p", { className: "text-muted-foreground px-1 text-xs tabular-nums", children: t('filterFooter', { count: shown.length, total: live.length }) })) : null, confirm] }));
109
+ }
@@ -1,4 +1,4 @@
1
- import { useMemo, useRef } from 'react';
1
+ import { useLayoutEffect, useMemo, useRef } from 'react';
2
2
  import { useSessionLogThread } from '@kortix/sdk/react';
3
3
  import { samePartsList } from '../../turn/same-parts.js';
4
4
  import { deriveIsRunning } from '../easy/easy-panel-logic.js';
@@ -11,9 +11,11 @@ import { deriveRunOutcome } from './run-outcome.js';
11
11
  /** The previous list while its elements are unchanged, so memos keyed on it hold across a text delta. */
12
12
  function useSameList(next) {
13
13
  const ref = useRef(next);
14
- if (!samePartsList(ref.current, next))
15
- ref.current = next;
16
- return ref.current;
14
+ const list = samePartsList(ref.current, next) ? ref.current : next;
15
+ useLayoutEffect(() => {
16
+ ref.current = list;
17
+ });
18
+ return list;
17
19
  }
18
20
  export function usePanelDerivations({ sessionId, projectId, projectSessionId, isSessionBusy }) {
19
21
  // The root thread's messages: the `fromV1` live view of the v1 rows while
@@ -1,7 +1,7 @@
1
1
  import type { SessionPanelValue } from '../session-panel-provider.js';
2
2
  import type { usePanelDerivations } from './use-panel-derivations.js';
3
- type Handoffs = Pick<SessionPanelValue, 'sessionId' | 'projectId' | 'projectSessionId' | 'detail' | 'handleOpenOutput' | 'openDetail' | 'openTerminal' | 'openAudit' | 'openBrowser' | 'openFiles'> & Pick<ReturnType<typeof usePanelDerivations>, 'isRunning' | 'outcome' | 'apps' | 'files' | 'steps'>;
4
- export declare function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openBrowser, openFiles, }: Handoffs): {
3
+ type Handoffs = Pick<SessionPanelValue, 'sessionId' | 'projectId' | 'projectSessionId' | 'detail' | 'handleOpenOutput' | 'openDetail' | 'openTerminal' | 'openAudit' | 'openReminders' | 'openBrowser' | 'openFiles'> & Pick<ReturnType<typeof usePanelDerivations>, 'isRunning' | 'outcome' | 'apps' | 'files' | 'steps'>;
4
+ export declare function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openBrowser, openFiles, }: Handoffs): {
5
5
  outputsDefaultOpen: boolean;
6
6
  pendingQuickView: {
7
7
  sessionId: string;
@@ -6,7 +6,7 @@ import { pathOutput, shouldAutoExpandOutputs, shouldAutoOpenPayoff, stepForCallI
6
6
  import { StepDetailBody } from '../easy/step-detail-body.js';
7
7
  import { StepIcon } from '../easy/step-icon.js';
8
8
  import { selectPrimaryDeliverable } from './output-priority.js';
9
- export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openBrowser, openFiles, }) {
9
+ export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openBrowser, openFiles, }) {
10
10
  // Auto-expand Outputs the moment a run finishes with something to show —
11
11
  // never on every render of an already-finished (or still-running) run, and
12
12
  // never on a failed one: a pile of half-written files is not a celebration.
@@ -150,6 +150,9 @@ export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRun
150
150
  else if (view === 'audit' && projectId && projectSessionId) {
151
151
  openAudit();
152
152
  }
153
+ else if (view === 'reminders' && projectId && projectSessionId) {
154
+ openReminders();
155
+ }
153
156
  else if (view === 'browser') {
154
157
  openBrowser(target);
155
158
  }
@@ -163,6 +166,7 @@ export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRun
163
166
  projectSessionId,
164
167
  openTerminal,
165
168
  openAudit,
169
+ openReminders,
166
170
  openBrowser,
167
171
  openFiles,
168
172
  ]);
@@ -59,7 +59,7 @@ function usePanelTransitions(panelGroupRef) {
59
59
  panels.forEach((panel) => {
60
60
  panel.style.transition = 'flex 300ms cubic-bezier(0.4, 0, 0.2, 1)';
61
61
  });
62
- }, []);
62
+ }, [panelGroupRef]);
63
63
  const disablePanelTransition = useCallback(() => {
64
64
  const el = panelGroupRef.current;
65
65
  if (!el)
@@ -68,7 +68,7 @@ function usePanelTransitions(panelGroupRef) {
68
68
  panels.forEach((panel) => {
69
69
  panel.style.transition = 'none';
70
70
  });
71
- }, []);
71
+ }, [panelGroupRef]);
72
72
  return { enablePanelTransition, disablePanelTransition };
73
73
  }
74
74
  // Keep the box current so a measurement landing at any moment has a real
@@ -95,7 +95,7 @@ function usePanelBoxObserver({ isMobile, panelGroupRef, panelBoxRef, }) {
95
95
  });
96
96
  observer.observe(el);
97
97
  return () => observer.disconnect();
98
- }, [isMobile]);
98
+ }, [isMobile, panelBoxRef, panelGroupRef]);
99
99
  }
100
100
  function usePanelSideSize({ isExpanded, isEasy, panelSplit, panelAspect, panelBoxRef, }) {
101
101
  // Easy mode opens at the panel's MINIMUM width (35/65): the cards are a
@@ -125,7 +125,7 @@ function usePanelSideSize({ isExpanded, isEasy, panelSplit, panelAspect, panelBo
125
125
  panelAspect,
126
126
  panelSplit,
127
127
  panelBox: panelBoxRef.current,
128
- }), [isExpanded, isEasy, panelAspect, panelSplit]);
128
+ }), [isExpanded, isEasy, panelAspect, panelSplit, panelBoxRef]);
129
129
  const mainSize = 100 - sideSize;
130
130
  return { sideSize, mainSize };
131
131
  }
@@ -185,6 +185,8 @@ function usePanelGlide(input) {
185
185
  panelAspect,
186
186
  sideSize,
187
187
  mainSize,
188
+ mainPanelRef,
189
+ sidePanelRef,
188
190
  ]);
189
191
  return isAnimating;
190
192
  }
@@ -218,5 +220,5 @@ function usePanelGlideFrames({ isAnimating, enablePanelTransition, sideSize, mai
218
220
  mainPanelRef.current?.resize(mainSize);
219
221
  });
220
222
  return () => cancelAnimationFrame(raf);
221
- }, [isAnimating, enablePanelTransition, sideSize, mainSize]);
223
+ }, [isAnimating, enablePanelTransition, sideSize, mainSize, mainPanelRef, sidePanelRef]);
222
224
  }