@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.
- package/README.md +8 -4
- package/dist/access/access/access-dialog.js +8 -5
- package/dist/access/access/copy-row.js +2 -2
- package/dist/access/sharing-picker.js +2 -1
- package/dist/browser/app-in-browser.js +5 -3
- package/dist/browser/browser-panel.js +6 -6
- package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
- package/dist/browser/sandbox-browser-chrome.js +4 -3
- package/dist/customize/agents/agent-access.d.ts +20 -0
- package/dist/customize/agents/agent-access.js +17 -0
- package/dist/customize/agents/agent-grant-pages.js +5 -6
- package/dist/customize/agents/agent-page.js +2 -1
- package/dist/customize/agents/agents-page.js +18 -1
- package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
- package/dist/customize/connectors/connector-routes.d.ts +3 -1
- package/dist/customize/connectors/connector-routes.js +18 -0
- package/dist/customize/connectors/connectors-page.js +23 -4
- package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
- package/dist/customize/providers/provider-accordion.js +2 -1
- package/dist/customize/providers/provider-connect.js +2 -1
- package/dist/customize/sections/component/telegram-channel-row.js +1 -1
- package/dist/customize/sections/connector-connection-modal.js +11 -9
- package/dist/customize/sections/connectors-view.js +11 -13
- package/dist/customize/sections/llm-provider/constants.d.ts +0 -1
- package/dist/customize/sections/llm-provider/constants.js +0 -2
- package/dist/customize/sections/llm-provider/models-tab-group.d.ts +1 -1
- package/dist/customize/sections/llm-provider/models-tab-group.js +7 -4
- package/dist/customize/sections/llm-provider/models-tab-row.d.ts +2 -18
- package/dist/customize/sections/llm-provider/models-tab-row.js +6 -30
- package/dist/customize/sections/llm-provider/models-tab.d.ts +0 -1
- package/dist/customize/sections/llm-provider/models-tab.js +1 -10
- package/dist/customize/sections/llm-provider/use-connected-providers.js +2 -2
- package/dist/customize/sections/llm-provider/utils.d.ts +1 -1
- package/dist/customize/sections/llm-provider/utils.js +7 -14
- package/dist/customize/sections/view/gateway/_shared.js +1 -2
- package/dist/customize/sections/view/gateway/gateway-routing.js +18 -20
- package/dist/customize/sections/view/sandbox-provider-coverage.d.ts +3 -18
- package/dist/customize/sections/view/sandbox-provider-coverage.js +7 -27
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/sandbox-tab.d.ts +2 -27
- package/dist/customize/settings/tabs/sandbox-tab.js +13 -112
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/shared/entity/entity-modal.d.ts +5 -1
- package/dist/customize/shared/entity/entity-modal.js +7 -4
- package/dist/customize/skills/remove-skill-button.d.ts +16 -0
- package/dist/customize/skills/remove-skill-button.js +42 -0
- package/dist/customize/skills/skills-page.js +4 -6
- package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
- package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
- package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
- package/dist/customize/triggers/schedule/trigger-list.d.ts +0 -2
- package/dist/customize/triggers/schedule/trigger-list.js +1 -18
- package/dist/customize/triggers/schedule/trigger-tile.d.ts +1 -32
- package/dist/customize/triggers/schedule/trigger-tile.js +1 -16
- package/dist/customize/triggers/schedule/when-event.js +1 -1
- package/dist/diff/diff-view.js +9 -10
- package/dist/files/editor/code-editor.js +2 -3
- package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
- package/dist/files/project/components/change-request-detail-dialog.js +3 -5
- package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
- package/dist/files/project/components/change-request-status-badge.js +27 -0
- package/dist/files/project/components/change-requests-panel.js +1 -1
- package/dist/files/project/components/drive-explorer.js +2 -1
- package/dist/files/project/components/drive-grid-view.js +11 -7
- package/dist/files/project/components/drive-list-view.js +4 -4
- package/dist/files/project/components/file-breadcrumbs.js +1 -1
- package/dist/files/project/components/file-explorer-page.js +1 -1
- package/dist/files/project/components/file-history-popover.js +1 -6
- package/dist/files/project/components/file-search.js +6 -1
- package/dist/files/project/components/open-change-request-dialog.js +2 -1
- package/dist/files/project/components/version-selector-logic.d.ts +11 -0
- package/dist/files/project/components/version-selector-logic.js +26 -0
- package/dist/files/project/components/version-selector.js +11 -20
- package/dist/files/project/hooks/use-change-requests.d.ts +14 -1
- package/dist/files/project/hooks/use-change-requests.js +44 -25
- package/dist/files/project/hooks/use-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +4 -6
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-renderer.d.ts +3 -1
- package/dist/files/renderers/pdf/pdf-renderer.js +2 -2
- package/dist/files/renderers/pdf/pdf-viewer.d.ts +3 -0
- package/dist/files/renderers/pdf/pdf-viewer.js +16 -7
- package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
- package/dist/files/renderers/show-content-renderer.js +1 -1
- package/dist/files/renderers/sqlite-renderer.js +8 -3
- package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
- package/dist/files/tree/components/file-tree-item.js +1 -1
- package/dist/files/viewer/file-content-renderer.d.ts +4 -1
- package/dist/files/viewer/file-content-renderer.js +2 -2
- package/dist/files/viewer/file-preview-modal.js +1 -1
- package/dist/files/viewer/use-file-content-state.js +1 -1
- package/dist/i18n/en.d.ts +36 -35
- package/dist/i18n/en.js +45 -44
- package/dist/index.d.ts +1 -1
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/hooks/use-notification-center.js +21 -15
- package/dist/internal/hooks/use-review-session-summary.d.ts +6 -0
- package/dist/internal/hooks/use-review-session-summary.js +11 -0
- package/dist/internal/lib/browser-error-noise.d.ts +1 -1
- package/dist/internal/lib/browser-error-noise.js +1 -1
- package/dist/internal/lib/browser-noise/rules/rejections.d.ts +10 -0
- package/dist/internal/lib/browser-noise/rules/rejections.js +27 -1
- package/dist/internal/lib/provider-branding.js +14 -3
- package/dist/internal/lib/trigger-clock.d.ts +42 -0
- package/dist/internal/lib/trigger-clock.js +34 -0
- package/dist/internal/lib/web-notifications.d.ts +6 -0
- package/dist/internal/lib/web-notifications.js +57 -7
- package/dist/internal/notifications/notification-bell.d.ts +13 -0
- package/dist/internal/notifications/notification-bell.js +1 -1
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -0
- package/dist/internal/reminders/reminder-filter.d.ts +12 -0
- package/dist/internal/reminders/reminder-filter.js +34 -0
- package/dist/internal/reminders/reminder-format.d.ts +6 -0
- package/dist/internal/reminders/reminder-format.js +20 -2
- package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
- package/dist/internal/stores/session-hover-store.d.ts +20 -5
- package/dist/internal/stores/session-hover-store.js +31 -13
- package/dist/internal/stores/session-panel-memory-store.js +2 -0
- package/dist/markdown/code/inline-code.js +8 -0
- package/dist/markdown/code/output-paths-context.d.ts +18 -0
- package/dist/markdown/code/output-paths-context.js +35 -0
- package/dist/markdown/genui/components/charts.js +6 -5
- package/dist/primitives/count-badge.d.ts +3 -1
- package/dist/primitives/count-badge.js +3 -3
- package/dist/primitives/glyph-paths-source.js +1 -1
- package/dist/primitives/glyph-paths.generated.js +1 -1
- package/dist/primitives/map.js +2 -1
- package/dist/primitives/preview-image.js +4 -2
- package/dist/primitives/textarea.js +1 -1
- package/dist/primitives/typed-title.js +12 -14
- package/dist/primitives/typing-animation.js +4 -2
- package/dist/providers/ui-provider.d.ts +57 -0
- package/dist/providers/ui-provider.js +4 -0
- package/dist/session/composer/ComposerCard.d.ts +2 -0
- package/dist/session/composer/ComposerCard.js +5 -1
- package/dist/session/composer/agent-selector.js +17 -5
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/composer-toolbar.js +4 -2
- package/dist/session/composer/composer-underbar.js +3 -1
- package/dist/session/composer/composer.js +34 -5
- package/dist/session/composer/draft/composer-draft.js +4 -4
- package/dist/session/composer/menus/slash-actions.d.ts +22 -2
- package/dist/session/composer/menus/slash-actions.js +20 -0
- package/dist/session/composer/menus/slash-items.js +19 -3
- package/dist/session/composer/pasted-text.js +3 -2
- package/dist/session/composer/stores/queued-draft-store.js +3 -2
- package/dist/session/header/reminder-parts.d.ts +294 -0
- package/dist/session/header/reminder-parts.js +120 -0
- package/dist/session/header/session-reminders-indicator.d.ts +13 -0
- package/dist/session/header/session-reminders-indicator.js +142 -35
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/models/model-availability.d.ts +23 -0
- package/dist/session/models/model-availability.js +20 -0
- package/dist/session/models/model-grouping.js +1 -4
- package/dist/session/models/model-selector.js +23 -14
- package/dist/session/models/use-model-connection-gate.js +4 -1
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/outcomes/session-outcomes-provider.d.ts +3 -1
- package/dist/session/outcomes/session-outcomes-provider.js +31 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
- package/dist/session/overrides/session-overrides-composer.d.ts +1 -1
- package/dist/session/overrides/session-overrides-composer.js +5 -1
- package/dist/session/panel/advanced/advanced-panel.js +3 -7
- package/dist/session/panel/easy/app-preview.js +12 -2
- package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
- package/dist/session/panel/easy/easy-panel-logic.js +1 -1
- package/dist/session/panel/easy/outputs-card.js +2 -1
- package/dist/session/panel/easy/panel-card.js +5 -3
- package/dist/session/panel/easy/preview-load.d.ts +14 -0
- package/dist/session/panel/easy/preview-load.js +15 -0
- package/dist/session/panel/easy/step-detail-body.js +2 -7
- package/dist/session/panel/mobile-tool-drawer.js +4 -2
- package/dist/session/panel/open-session-quick-view.js +2 -1
- package/dist/session/panel/panel-fallback.js +8 -4
- package/dist/session/panel/session-panel-provider.d.ts +1 -0
- package/dist/session/panel/session-panel-provider.js +19 -3
- package/dist/session/panel/session-reminders-panel.d.ts +4 -0
- package/dist/session/panel/session-reminders-panel.js +109 -0
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
- package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.d.ts +0 -10
- package/dist/session/print/session-print-header.js +25 -9
- package/dist/session/status/session-surface.js +1 -1
- package/dist/session/thread/optimistic-turn.d.ts +5 -1
- package/dist/session/thread/optimistic-turn.js +8 -6
- package/dist/session/thread/session-chat.d.ts +6 -1
- package/dist/session/thread/session-chat.js +34 -12
- package/dist/session/thread/session-history.js +3 -1
- package/dist/session/thread/session-thread.d.ts +10 -1
- package/dist/session/thread/session-thread.js +2 -2
- package/dist/session/thread/transcript.js +13 -7
- package/dist/session/tool/shared/file-list.js +19 -2
- package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
- package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
- package/dist/session/tool/shared/infrastructure-output.js +9 -5
- package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
- package/dist/session/tool/shared/infrastructure-rows.js +7 -5
- package/dist/session/tool/tools/bash-tool.js +10 -6
- package/dist/session/tool/tools/web-search-tool.js +25 -3
- package/dist/session/turn/burst-step-body.js +4 -0
- package/dist/session/turn/recovered-failures.d.ts +8 -0
- package/dist/session/turn/recovered-failures.js +32 -0
- package/dist/session/turn/streaming-cadence.js +5 -8
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-panel.js +3 -5
- package/dist/theme/theme.js +2 -1
- package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
- package/dist/workspace/review/review-center.js +13 -10
- package/dist/workspace/session-workspace.js +5 -9
- package/dist/workspace/sessions/deleted-session-row.js +5 -2
- package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
- package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
- package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
- package/dist/workspace/sidebar/project-session-list.js +52 -55
- package/dist/workspace/sidebar/session-brief-hover-card.d.ts +35 -18
- package/dist/workspace/sidebar/session-brief-hover-card.js +114 -37
- package/dist/workspace/sidebar/session-status-mark.d.ts +15 -1
- package/dist/workspace/sidebar/session-status-mark.js +63 -17
- package/package.json +7 -3
- package/skills/kortix-ui/SKILL.md +1 -1
- package/dist/customize/sections/view/sandbox-provider-result.d.ts +0 -47
- 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
|
|
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
|
-
|
|
29
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
34
|
+
const [prevDefaultExpanded, setPrevDefaultExpanded] = useState(defaultExpanded);
|
|
35
|
+
if (defaultExpanded !== prevDefaultExpanded) {
|
|
36
|
+
setPrevDefaultExpanded(defaultExpanded);
|
|
35
37
|
if (defaultExpanded)
|
|
36
38
|
setExpanded(true);
|
|
37
|
-
}
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
49
|
-
? '
|
|
50
|
-
:
|
|
51
|
-
|
|
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
|
, {
|
|
@@ -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,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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
}
|