@kortix/ui 0.14.5 → 0.14.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_vendor/kortix-shared.js +489 -381
- 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-authority-card.js +1 -2
- package/dist/customize/agents/agent-detail-aside.js +15 -14
- package/dist/customize/agents/agent-drives-section.js +5 -6
- package/dist/customize/agents/agent-grant-pages.js +73 -76
- package/dist/customize/agents/agent-nav.prototype.d.ts +35 -0
- package/dist/customize/agents/agent-nav.prototype.js +95 -0
- package/dist/customize/agents/agent-page.js +59 -23
- package/dist/customize/agents/agent-people-section.js +18 -17
- package/dist/customize/agents/agent-triggers-section.js +6 -5
- 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/catalog/catalog-entry.d.ts +14 -0
- package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
- package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
- package/dist/customize/connectors/catalog/connector-browse.js +18 -5
- 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/app-page.js +19 -5
- package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
- package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
- package/dist/customize/connectors/detail/connector-modal.js +18 -101
- package/dist/customize/connectors/detail/connector-notice.js +6 -2
- package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
- package/dist/customize/connectors/detail/connector-page.js +26 -13
- 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/custom-provider-form.js +24 -7
- package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
- package/dist/customize/sections/llm-provider/types.d.ts +3 -0
- package/dist/customize/sections/llm-provider/utils.js +2 -2
- package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
- package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
- package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
- package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
- package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
- package/dist/customize/sections/view/agent-editor.js +3 -3
- package/dist/customize/sections/view/gateway/gateway-routing.js +18 -21
- package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
- package/dist/customize/sections/view/grant-mode-field.js +10 -18
- package/dist/customize/sections/view/permission-editor.js +11 -8
- package/dist/customize/sections/view/secrets-view.js +2 -1
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
- package/dist/customize/settings/tabs/general-tab.js +33 -11
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
- package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/skills/skills-page.js +3 -5
- 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/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/api/runtime-files.js +3 -2
- 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/file-source.js +18 -1
- package/dist/files/project/hooks/use-file-content.js +2 -0
- package/dist/files/project/hooks/use-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +0 -5
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
- 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-header.js +1 -1
- package/dist/files/viewer/file-preview-modal.d.ts +1 -1
- package/dist/files/viewer/file-preview-modal.js +13 -3
- package/dist/files/viewer/file-source.d.ts +3 -1
- package/dist/files/viewer/use-file-content-state.js +27 -8
- package/dist/files/viewer/viewer-actions.js +2 -2
- package/dist/i18n/en.d.ts +355 -12
- package/dist/i18n/en.js +360 -17
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/files/types.d.ts +2 -0
- package/dist/internal/goals/goal-meta.d.ts +30 -0
- package/dist/internal/goals/goal-meta.js +55 -0
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/lib/query-client-singleton.d.ts +9 -3
- package/dist/internal/lib/query-client-singleton.js +18 -12
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -0
- package/dist/internal/share/session-shares-modal.js +3 -7
- package/dist/internal/stores/kortix-computer-store.js +14 -2
- package/dist/internal/stores/session-hover-store.d.ts +32 -0
- package/dist/internal/stores/session-hover-store.js +38 -0
- package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
- package/dist/internal/stores/sidebar-nav-store.js +3 -1
- package/dist/lib/routes.d.ts +2 -0
- package/dist/lib/routes.js +1 -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/map.js +2 -1
- package/dist/primitives/preview-image.js +4 -2
- package/dist/primitives/settings-section-header.d.ts +1 -1
- 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/session/composer/agent-selector.js +6 -1
- package/dist/session/composer/attachment-tile.d.ts +0 -2
- package/dist/session/composer/attachment-tile.js +0 -8
- package/dist/session/composer/clipboard-files.d.ts +2 -1
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
- package/dist/session/composer/composer-draft-recovery.js +0 -13
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/context-ring.d.ts +1 -1
- package/dist/session/composer/context-ring.js +2 -2
- package/dist/session/composer/context-usage.d.ts +3 -6
- package/dist/session/composer/context-usage.js +3 -6
- package/dist/session/composer/draft/composer-draft.js +4 -4
- package/dist/session/composer/inbox-row-claims.d.ts +2 -2
- package/dist/session/composer/inbox-row-claims.js +1 -1
- package/dist/session/composer/pasted-text.js +3 -2
- package/dist/session/composer/queue-projection.d.ts +3 -2
- package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
- package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
- package/dist/session/composer/session-composer-readiness.d.ts +7 -22
- package/dist/session/composer/session-composer-readiness.js +12 -40
- package/dist/session/composer/stores/queued-draft-store.js +3 -2
- package/dist/session/composer/token-progress.d.ts +1 -1
- package/dist/session/composer/token-progress.js +2 -2
- package/dist/session/header/reminder-parts.d.ts +11 -2
- package/dist/session/header/reminder-parts.js +9 -4
- package/dist/session/header/session-goal-indicator.d.ts +1 -0
- package/dist/session/header/session-goal-indicator.js +41 -0
- package/dist/session/header/session-site-header.js +2 -1
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -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/session-action-panel-column.js +11 -1
- package/dist/session/panel/session-audit-shared.d.ts +0 -4
- package/dist/session/panel/session-audit-shared.js +1 -1
- package/dist/session/panel/session-changes-shared.d.ts +2 -1
- package/dist/session/panel/session-panel-provider.js +6 -2
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.js +9 -8
- package/dist/session/prompts/session-permission-prompt.js +6 -3
- package/dist/session/status/provisioning-failure.d.ts +2 -1
- package/dist/session/status/session-error-banner.js +8 -6
- package/dist/session/status/session-starting-loader.d.ts +24 -31
- package/dist/session/status/session-starting-loader.js +32 -81
- package/dist/session/thread/chat-minimap-items.d.ts +4 -4
- package/dist/session/thread/first-prompt-handover.d.ts +3 -2
- package/dist/session/thread/message-parsing.d.ts +3 -3
- package/dist/session/thread/session-chat.js +15 -19
- package/dist/session/thread/session-history-scroll.d.ts +2 -1
- package/dist/session/thread/session-history.js +3 -1
- package/dist/session/thread/session-thread.js +1 -1
- 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/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/pty-connection.d.ts +5 -4
- package/dist/terminal/session-terminal-panel.js +3 -5
- package/dist/theme/theme.js +2 -1
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/index.js +1 -0
- package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
- package/dist/workspace/review/review-center.js +13 -10
- package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
- package/dist/workspace/saved-session-skeleton.d.ts +5 -1
- package/dist/workspace/saved-session-skeleton.js +5 -4
- package/dist/workspace/session-workspace.js +9 -14
- 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-goals-nav.d.ts +6 -0
- package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
- 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/modal/session-embed-section.d.ts +25 -0
- package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
- package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
- package/dist/workspace/sidebar/nav-row-meta.js +14 -2
- package/dist/workspace/sidebar/project-nav-items.js +3 -0
- package/dist/workspace/sidebar/project-session-list.js +12 -3
- package/dist/workspace/sidebar/session-brief-hover-card.js +5 -8
- package/package.json +4 -4
- package/skills/kortix-ui/references/modules.md +2 -0
- package/dist/session/status/use-readiness-settling.d.ts +0 -18
- package/dist/session/status/use-readiness-settling.js +0 -43
- package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
- package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
|
@@ -9,7 +9,7 @@ export type PtyCloseAction = 'ended' | 'reconnect' | 'replace';
|
|
|
9
9
|
* terminal asks `GET /kortix/pty`, which passes the same control-plane gate and
|
|
10
10
|
* returns a readable body. That GET never wakes a box.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
type PtyAttachProbe = 'reachable' | 'not-ready' | 'unreachable';
|
|
13
13
|
/** `null` = the PTY list answered. Anything else is the error it threw. */
|
|
14
14
|
export declare function classifyPtyAttachProbe(error: unknown): PtyAttachProbe;
|
|
15
15
|
/**
|
|
@@ -36,10 +36,10 @@ export declare const PTY_WAKE_RETRY_MS = 2000;
|
|
|
36
36
|
export declare const PTY_WAKE_DEADLINE_MS: number;
|
|
37
37
|
/** Consecutive non-wake failures before the terminal stops and offers Retry. */
|
|
38
38
|
export declare const PTY_MAX_ATTACH_FAILURES = 5;
|
|
39
|
-
|
|
39
|
+
type PtyAttachPause = 'asleep' | 'failed';
|
|
40
40
|
/** What the attach overlay shows. `null` means the shell is live. */
|
|
41
41
|
export type AttachPhase = 'connecting' | 'waking' | 'reconnecting' | PtyAttachPause | null;
|
|
42
|
-
|
|
42
|
+
type PtyAttachStep = {
|
|
43
43
|
kind: 'retry';
|
|
44
44
|
delayMs: number;
|
|
45
45
|
phase: 'waking' | 'reconnecting';
|
|
@@ -56,7 +56,7 @@ export declare function nextPtyAttachStep(input: {
|
|
|
56
56
|
/** How long the box has reported not-ready while a wake was armed. */
|
|
57
57
|
wakingForMs: number;
|
|
58
58
|
}): PtyAttachStep;
|
|
59
|
-
|
|
59
|
+
type TerminalPanelState = 'connecting' | 'error' | 'empty' | 'terminal';
|
|
60
60
|
export declare function deriveTerminalPanelState(input: {
|
|
61
61
|
hasServerUrl: boolean;
|
|
62
62
|
serverWaitExpired: boolean;
|
|
@@ -81,3 +81,4 @@ export declare function classifyPtyClose(input: {
|
|
|
81
81
|
}): PtyCloseAction;
|
|
82
82
|
/** Prevent a bad runtime from creating terminals forever in a replacement loop. */
|
|
83
83
|
export declare function shouldAutoReplaceTerminal(replacementAttempt: number): boolean;
|
|
84
|
+
export {};
|
|
@@ -94,11 +94,9 @@ export function SessionTerminalPanel({ sessionId, projectId, projectSessionId, h
|
|
|
94
94
|
setOptimisticPty(null);
|
|
95
95
|
setTerminalPty(sessionId, null);
|
|
96
96
|
}, [sessionId, setTerminalPty]);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
}, [listedPty, optimisticPty?.id]);
|
|
97
|
+
if (listedPty && optimisticPty?.id === listedPty.id) {
|
|
98
|
+
setOptimisticPty(null);
|
|
99
|
+
}
|
|
102
100
|
useEffect(() => {
|
|
103
101
|
if (!terminalPtyId || isLoading || !ptys || pty || optimisticPty?.id === terminalPtyId)
|
|
104
102
|
return;
|
package/dist/theme/theme.js
CHANGED
|
@@ -123,8 +123,9 @@ function checkName(name) {
|
|
|
123
123
|
return name;
|
|
124
124
|
}
|
|
125
125
|
function steps(field, allowed, values) {
|
|
126
|
+
const allowedSet = new Set(allowed);
|
|
126
127
|
return Object.entries(values ?? {}).flatMap(([k, v]) => {
|
|
127
|
-
if (!
|
|
128
|
+
if (!allowedSet.has(k))
|
|
128
129
|
throw new Error(`@kortix/ui theme: unknown ${field} step "${k}"`);
|
|
129
130
|
return v === undefined ? [] : [[k, v]];
|
|
130
131
|
});
|
|
@@ -23,3 +23,4 @@ export { ProjectSessionsView } from './sessions/project-sessions-view.js';
|
|
|
23
23
|
export { ProjectSessionList } from './sidebar/project-session-list.js';
|
|
24
24
|
export { ProjectSidebar } from './sidebar/project-sidebar.js';
|
|
25
25
|
export { ShareSessionModal } from './sidebar/modal/share-session-modal.js';
|
|
26
|
+
export { formatGoalTime, GoalHealthDot, goalHref, goalNeedsYou, goalOpenAsks, goalProgress, GoalStatusBadge, useGoalClock, useGoalIssueText, } from '../internal/goals/goal-meta.js';
|
package/dist/workspace/index.js
CHANGED
|
@@ -23,3 +23,4 @@ export { ProjectSessionsView } from './sessions/project-sessions-view.js';
|
|
|
23
23
|
export { ProjectSessionList } from './sidebar/project-session-list.js';
|
|
24
24
|
export { ProjectSidebar } from './sidebar/project-sidebar.js';
|
|
25
25
|
export { ShareSessionModal } from './sidebar/modal/share-session-modal.js';
|
|
26
|
+
export { formatGoalTime, GoalHealthDot, goalHref, goalNeedsYou, goalOpenAsks, goalProgress, GoalStatusBadge, useGoalClock, useGoalIssueText, } from '../internal/goals/goal-meta.js';
|
|
@@ -10,7 +10,7 @@ import { Github } from '../../../internal/icons/github.js';
|
|
|
10
10
|
import { cn } from '../../../internal/lib/utils.js';
|
|
11
11
|
import { CaretDownIcon as CaretDown, GitBranchIcon as GitBranch, MagnifyingGlassIcon as Search, } from '@phosphor-icons/react';
|
|
12
12
|
import { useTranslations } from '../../../i18n/index.js';
|
|
13
|
-
import {
|
|
13
|
+
import { useMemo, useState } from 'react';
|
|
14
14
|
/** Varied so the placeholder reads as a list of names, not a barcode. */
|
|
15
15
|
const SKELETON_WIDTHS = ['w-3/5', 'w-2/5', 'w-1/2', 'w-3/4', 'w-1/3'];
|
|
16
16
|
/** Above this many options the list is fixed-height (see `SearchPicker`). */
|
|
@@ -28,13 +28,14 @@ function SearchPicker({ value, options, loading, disabled, loadingLabel, placeho
|
|
|
28
28
|
const filtered = normalizedSearch
|
|
29
29
|
? options.filter((option) => option.keywords.includes(normalizedSearch))
|
|
30
30
|
: options;
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const handleOpenChange = (next) => {
|
|
32
|
+
setOpen(next);
|
|
33
|
+
if (!next) {
|
|
33
34
|
setSearch('');
|
|
34
35
|
onSearchChange?.('');
|
|
35
36
|
}
|
|
36
|
-
}
|
|
37
|
-
return (_jsxs(Popover, { open: open, onOpenChange:
|
|
37
|
+
};
|
|
38
|
+
return (_jsxs(Popover, { open: open, onOpenChange: handleOpenChange, modal: false, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", disabled: disabled, className: cn(triggerVariants({ size: 'md' }), 'w-full'), children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground flex shrink-0 [&_svg]:size-4", children: icon }), _jsx("span", { className: cn('min-w-0 truncate', !selected && 'text-muted-foreground'), children: selected?.label ?? (firstLoad ? loadingLabel : placeholder) })] }), loading ? (_jsx(Loading, { variant: "spokes", className: "text-muted-foreground! size-4 shrink-0" })) : (_jsx(CaretDown, { className: cn(TRIGGER_CARET_CLASS, TRIGGER_ICON_SIZE.md) }))] }) }), _jsxs(PopoverContent, { side: "top", align: "start", avoidCollisions: false, className: "w-[var(--radix-popover-trigger-width)] overflow-hidden p-0", children: [_jsx("div", { className: "border-border border-b py-1", children: _jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(Search, {}) }), _jsx(InputGroupSearchInput, { size: "xs", className: "border-transparent focus:border-transparent focus:ring-0", value: search, onChange: (event) => {
|
|
38
39
|
setSearch(event.target.value);
|
|
39
40
|
onSearchChange?.(event.target.value);
|
|
40
41
|
}, placeholder: searchPlaceholder, autoCapitalize: "none", autoCorrect: "off", autoFocus: true })] }) }), firstLoad ? (
|
|
@@ -49,7 +50,7 @@ function SearchPicker({ value, options, loading, disabled, loadingLabel, placeho
|
|
|
49
50
|
const active = option.value === value;
|
|
50
51
|
return (_jsx("li", { children: _jsxs("button", { type: "button", "aria-pressed": active, className: cn(menuRow('md', 'default'), 'text-left', option.description && 'items-start', active && 'bg-primary/10 text-foreground'), onClick: () => {
|
|
51
52
|
onValueChange(option.value);
|
|
52
|
-
|
|
53
|
+
handleOpenChange(false);
|
|
53
54
|
}, children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "text-foreground truncate", children: option.label }), option.description ? (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: option.description })) : null] }), option.badge ? _jsx("span", { className: "shrink-0", children: option.badge }) : null] }) }, option.value));
|
|
54
55
|
}) }))] })] }));
|
|
55
56
|
}
|
|
@@ -347,15 +347,15 @@ export function ReviewCenter({ initialItems, segment: segmentProp, onSegmentChan
|
|
|
347
347
|
setSelectMode(false);
|
|
348
348
|
}
|
|
349
349
|
// If the active session filter no longer has items in this segment, reset it.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
}, [sessionOpts, sessionFilter]);
|
|
350
|
+
if (sessionFilter !== 'all' && !sessionOpts.some((s) => s.sessionId === sessionFilter)) {
|
|
351
|
+
setSessionFilter('all');
|
|
352
|
+
}
|
|
355
353
|
// Keep the focus cursor in range as the visible list changes.
|
|
356
|
-
|
|
354
|
+
const [visibleCountSeen, setVisibleCountSeen] = useState(null);
|
|
355
|
+
if (visible.length !== visibleCountSeen) {
|
|
356
|
+
setVisibleCountSeen(visible.length);
|
|
357
357
|
setFocusedIdx((i) => Math.max(0, Math.min(i, visible.length - 1)));
|
|
358
|
-
}
|
|
358
|
+
}
|
|
359
359
|
// Select mode: DOM focus follows the cursor (j/k, a toggled row), so Space
|
|
360
360
|
// and Enter act on the row the user sees, and a pointer entry that
|
|
361
361
|
// unmounts the clicked checkbox keeps focus on its row.
|
|
@@ -377,12 +377,14 @@ export function ReviewCenter({ initialItems, segment: segmentProp, onSegmentChan
|
|
|
377
377
|
// mutation (there's no per-call settle callback threaded through `apply`),
|
|
378
378
|
// so clear the pending marker here rather than leave a button spinning
|
|
379
379
|
// forever if the item already left the list (e.g. a stale poll raced it).
|
|
380
|
-
|
|
380
|
+
const [initialItemsSeen, setInitialItemsSeen] = useState(initialItems);
|
|
381
|
+
if (initialItems !== initialItemsSeen) {
|
|
382
|
+
setInitialItemsSeen(initialItems);
|
|
381
383
|
if (initialItems)
|
|
382
384
|
setItems(initialItems);
|
|
383
385
|
setPendingId(null);
|
|
384
386
|
setPendingDecision(null);
|
|
385
|
-
}
|
|
387
|
+
}
|
|
386
388
|
// Detect items that arrived since the user last looked → flag them "fresh".
|
|
387
389
|
// The first load of each segment seeds its known set silently (nothing is
|
|
388
390
|
// "new" on open, or on a first visit to a segment).
|
|
@@ -711,9 +713,10 @@ export function ReviewCenter({ initialItems, segment: segmentProp, onSegmentChan
|
|
|
711
713
|
},
|
|
712
714
|
];
|
|
713
715
|
const confirmCount = bulkIds[confirm.verb].length;
|
|
716
|
+
const confirmIds = new Set(bulkIds[confirm.verb]);
|
|
714
717
|
// Every item the verb will decide: which app, what it does, to what, how risky.
|
|
715
718
|
const confirmList = (_jsx("ul", { className: "border-border divide-border max-h-72 w-full min-w-0 divide-y overflow-y-auto rounded-lg border", children: selectedItems
|
|
716
|
-
.filter((i) =>
|
|
719
|
+
.filter((i) => confirmIds.has(i.id))
|
|
717
720
|
.map((i) => {
|
|
718
721
|
const risk = RISK_BADGE[i.risk];
|
|
719
722
|
// The arguments, as the approval modal reads them: the content
|
|
@@ -23,12 +23,12 @@ export declare const LINE_WIDTHS: readonly ["w-full", "w-11/12", "w-5/6"];
|
|
|
23
23
|
export declare const LAST_LINE_WIDTHS: readonly ["w-1/3", "w-2/5", "w-1/2", "w-3/5", "w-2/3", "w-3/4"];
|
|
24
24
|
/** Tailwind's `animate-pulse` period, which the `Skeleton` primitive runs. */
|
|
25
25
|
export declare const PULSE_PERIOD_MS = 2000;
|
|
26
|
-
|
|
26
|
+
interface SkeletonLine {
|
|
27
27
|
width: string;
|
|
28
28
|
/** The row's place in the pulse wave; see `pulseDelayMs`. */
|
|
29
29
|
phase: number;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
type SkeletonParagraph = {
|
|
32
32
|
kind: 'text';
|
|
33
33
|
lines: SkeletonLine[];
|
|
34
34
|
}
|
|
@@ -37,7 +37,7 @@ export type SkeletonParagraph = {
|
|
|
37
37
|
kind: 'block';
|
|
38
38
|
phase: number;
|
|
39
39
|
};
|
|
40
|
-
|
|
40
|
+
interface SkeletonTurn {
|
|
41
41
|
prompt: SkeletonLine & {
|
|
42
42
|
tall: boolean;
|
|
43
43
|
};
|
|
@@ -63,3 +63,4 @@ export declare function savedSessionSkeletonShape(sessionId: string): SkeletonSh
|
|
|
63
63
|
* its cycle, so every row is on screen and moving from the first frame.
|
|
64
64
|
*/
|
|
65
65
|
export declare function pulseDelayMs(phase: number, phases: number): number;
|
|
66
|
+
export {};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { SessionStartStage } from '@kortix/sdk';
|
|
2
|
-
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
export declare function SavedSessionSkeleton({ projectId, sessionId, stage, status, }: {
|
|
3
4
|
projectId: string;
|
|
4
5
|
/** The route's session id. */
|
|
5
6
|
sessionId: string;
|
|
6
7
|
stage: SessionStartStage;
|
|
8
|
+
/** Drawn in place of the skeleton rows: there is no saved conversation to
|
|
9
|
+
* stand in for, only the wake's status. */
|
|
10
|
+
status?: ReactNode;
|
|
7
11
|
}): import("react").JSX.Element;
|
|
@@ -15,8 +15,9 @@ import { useMemo } from 'react';
|
|
|
15
15
|
* The control plane keeps a saved copy of every session's conversation and
|
|
16
16
|
* answers in one round trip; the computer takes 5-240 s to wake. So a session
|
|
17
17
|
* that has a saved copy opens on this — its header, skeleton turns, and the
|
|
18
|
-
* composer's outline — and the conversation replaces it in one step.
|
|
19
|
-
*
|
|
18
|
+
* composer's outline — and the conversation replaces it in one step. A session
|
|
19
|
+
* with nothing to read (`resolveResumeOverlay`) gets the same frame with a
|
|
20
|
+
* `status` in place of the rows.
|
|
20
21
|
*
|
|
21
22
|
* Built from the geometry the real chat uses (`SessionLayout`, `SessionBodyRow`,
|
|
22
23
|
* `SESSION_TRANSCRIPT_CLASS`, `COMPOSER_SHELL_CLASS`), so the crossfade into
|
|
@@ -36,8 +37,8 @@ import { useMemo } from 'react';
|
|
|
36
37
|
* open waits 0.3-2.3 s for the server's copy, so its rows come 200 ms later.
|
|
37
38
|
*/
|
|
38
39
|
const APPEAR_AFTER_A_MOMENT = 'animate-in fade-in fill-mode-both duration-normal delay-[var(--duration-moderate)]';
|
|
39
|
-
export function SavedSessionSkeleton({ projectId, sessionId, stage, }) {
|
|
40
|
+
export function SavedSessionSkeleton({ projectId, sessionId, stage, status, }) {
|
|
40
41
|
const t = useTranslations('sessionPage');
|
|
41
42
|
const shape = useMemo(() => savedSessionSkeletonShape(sessionId), [sessionId]);
|
|
42
|
-
return (_jsx(SessionLayout, { sessionId: sessionId, projectId: projectId, projectSessionId: sessionId, transient: true, bootStage: stage === 'ready' ? null : stage, children: _jsxs("div", { className: "bg-background relative flex h-full flex-col", children: [_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: "" }), _jsxs(SessionBodyRow, { transient: true, children: [_jsx("div", { className: "relative z-10 min-h-0 flex-1 overflow-hidden", children: _jsx("div", { role: "status", "aria-busy": "true", "aria-label": t('loadingConversation'), "data-testid": "saved-session-skeleton", className: cn(SESSION_TRANSCRIPT_CLASS, APPEAR_AFTER_A_MOMENT), children: _jsx(SavedSessionSkeletonRows, { shape: shape }) }) }), _jsx("div", { "aria-hidden": true, className: cn(COMPOSER_SHELL_CLASS, 'pb-8', APPEAR_AFTER_A_MOMENT), children: _jsx(SkeletonBar, { phase: shape.composerPhase, phases: shape.phases, className: "h-28 w-full rounded-xl" }) })] })] }) }));
|
|
43
|
+
return (_jsx(SessionLayout, { sessionId: sessionId, projectId: projectId, projectSessionId: sessionId, transient: true, bootStage: stage === 'ready' ? null : stage, children: _jsxs("div", { className: "bg-background relative flex h-full flex-col", children: [_jsx(SessionSiteHeader, { sessionId: sessionId, sessionTitle: "" }), _jsxs(SessionBodyRow, { transient: true, children: [_jsx("div", { className: "relative z-10 min-h-0 flex-1 overflow-hidden", children: status ?? (_jsx("div", { role: "status", "aria-busy": "true", "aria-label": t('loadingConversation'), "data-testid": "saved-session-skeleton", className: cn(SESSION_TRANSCRIPT_CLASS, APPEAR_AFTER_A_MOMENT), children: _jsx(SavedSessionSkeletonRows, { shape: shape }) })) }), _jsx("div", { "aria-hidden": true, className: cn(COMPOSER_SHELL_CLASS, 'pb-8', APPEAR_AFTER_A_MOMENT), children: _jsx(SkeletonBar, { phase: shape.composerPhase, phases: shape.phases, className: "h-28 w-full rounded-xl" }) })] })] }) }));
|
|
43
44
|
}
|
|
@@ -21,7 +21,7 @@ import { useKortixComputerStore } from '../internal/stores/kortix-computer-store
|
|
|
21
21
|
import { sessionPreviewTabId, useSessionBrowserStore, } from '../internal/stores/session-browser-store.js';
|
|
22
22
|
import { ProjectScope } from '../internal/project-scope.js';
|
|
23
23
|
import { SessionThread } from '../session/thread/session-thread.js';
|
|
24
|
-
import { memo, useCallback,
|
|
24
|
+
import { memo, useCallback, useState } from 'react';
|
|
25
25
|
export const SessionLayout = memo(function SessionLayout({ sessionId, projectId, projectSessionId, children, bootStage = null, transient = false, }) {
|
|
26
26
|
const isMobile = useIsMobile();
|
|
27
27
|
const booting = !!bootStage;
|
|
@@ -106,10 +106,9 @@ function useSessionPanelSurfaces({ sessionId, projectId, projectSessionId, bootS
|
|
|
106
106
|
setIsSidePanelOpen(!isSidePanelOpen);
|
|
107
107
|
}, [isSidePanelOpen, setIsSidePanelOpen]);
|
|
108
108
|
const panelHeader = (_jsx(PanelHeaderSwitcher, { view: effectiveView, onChangeView: (v) => setPanelView(sessionId, v), isSidePanelOpen: isSidePanelOpen, onTogglePanel: handleTogglePanel, auditBadge: auditPendingCount, onToggleMode: togglePanelMode }));
|
|
109
|
-
// While booting, the panel is JUST the
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// centered. The runtime-coupled views (Actions/Files/Terminal/Browser) need a
|
|
109
|
+
// While booting, the panel is JUST the wake status — empty unless the wake
|
|
110
|
+
// needs attention, and then dead-center in the card — with no header bar.
|
|
111
|
+
// The runtime-coupled views (Actions/Files/Terminal/Browser) need a
|
|
113
112
|
// live sandbox, so they only render once booted.
|
|
114
113
|
//
|
|
115
114
|
// Easy mode has no header either: it is the three cards and nothing else. No
|
|
@@ -120,7 +119,7 @@ function useSessionPanelSurfaces({ sessionId, projectId, projectSessionId, bootS
|
|
|
120
119
|
// own close DISCARDS the detail (it is done with), while ⌘I only puts it
|
|
121
120
|
// away — press it again and the same detail comes back.
|
|
122
121
|
const effectivePanelHeader = booting || isEasy ? null : panelHeader;
|
|
123
|
-
const effectivePanelBody = booting ? (_jsx(SessionStartingLoader, {
|
|
122
|
+
const effectivePanelBody = booting ? (_jsx(SessionStartingLoader, { projectId: projectId, sessionId: projectSessionId })) : (panelBody);
|
|
124
123
|
return { effectivePanelHeader, effectivePanelBody };
|
|
125
124
|
}
|
|
126
125
|
function useSessionPanelBody({ sessionId, projectId, projectSessionId, isEasy, effectiveView, }) {
|
|
@@ -129,15 +128,11 @@ function useSessionPanelBody({ sessionId, projectId, projectSessionId, isEasy, e
|
|
|
129
128
|
const showTerminal = !isEasy && effectiveView === 'terminal';
|
|
130
129
|
const showAudit = !isEasy && effectiveView === 'audit';
|
|
131
130
|
const [terminalActivated, setTerminalActivated] = useState(false);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
setTerminalActivated(true);
|
|
135
|
-
}, [showTerminal]);
|
|
131
|
+
if (showTerminal && !terminalActivated)
|
|
132
|
+
setTerminalActivated(true);
|
|
136
133
|
const [browserActivated, setBrowserActivated] = useState(false);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
setBrowserActivated(true);
|
|
140
|
-
}, [showBrowser]);
|
|
134
|
+
if (showBrowser && !browserActivated)
|
|
135
|
+
setBrowserActivated(true);
|
|
141
136
|
// Easy mode's body is the detail shell and nothing else — the cards moved to
|
|
142
137
|
// the floating overlay over the chat. The Advanced branches below are kept
|
|
143
138
|
// intact (Advanced is disabled, not deleted) and are unreachable while
|
|
@@ -5,6 +5,7 @@ import Loading from '../../primitives/loading.js';
|
|
|
5
5
|
import { getSessionDisplayTitle } from '../../session/actions/project-session-list-helpers.js';
|
|
6
6
|
import { useLocale, useTranslations } from '../../i18n/index.js';
|
|
7
7
|
import { relativeTime } from '../../internal/lib/relative-time.js';
|
|
8
|
+
import { useMemo } from 'react';
|
|
8
9
|
/**
|
|
9
10
|
* One row of Recently deleted (KRTX-2853): a session the viewer deleted (or,
|
|
10
11
|
* as a manager, one they can open), still inside its restore window. Not a
|
|
@@ -16,10 +17,12 @@ export function DeletedSessionRow({ session, restoring, onRestore, }) {
|
|
|
16
17
|
const title = getSessionDisplayTitle(session);
|
|
17
18
|
const deletedAt = session.deleted_at ? new Date(session.deleted_at) : null;
|
|
18
19
|
const restoreUntil = session.restore_until ? new Date(session.restore_until) : null;
|
|
19
|
-
const
|
|
20
|
+
const exactFormat = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }), [locale]);
|
|
21
|
+
const dayFormat = useMemo(() => new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric' }), [locale]);
|
|
22
|
+
const exact = (date) => exactFormat.format(date);
|
|
20
23
|
return (_jsxs("div", { className: "bg-popover flex items-center gap-3 rounded-md border px-3 py-2", "data-deleted-session-id": session.session_id, children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-foreground truncate text-sm", children: title }), _jsxs("p", { className: "text-muted-foreground truncate text-xs", children: [deletedAt ? (_jsx("time", { dateTime: session.deleted_at, title: exact(deletedAt), children: t('deletedAgo', {
|
|
21
24
|
when: relativeTime(session.deleted_at, locale, undefined, { style: 'long' }),
|
|
22
25
|
}) })) : null, deletedAt && restoreUntil ? ' · ' : null, restoreUntil ? (_jsx("time", { dateTime: session.restore_until, title: exact(restoreUntil), children: t('restorableUntil', {
|
|
23
|
-
date:
|
|
26
|
+
date: dayFormat.format(restoreUntil),
|
|
24
27
|
}) })) : null] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: restoring, onClick: () => onRestore(session), "aria-label": t('restoreNamed', { title }), children: [restoring ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, t('restore')] })] }));
|
|
25
28
|
}
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { PromoNavRow } from './promo-nav-row.js';
|
|
4
4
|
import { LaptopIcon } from '@phosphor-icons/react';
|
|
5
|
-
import { useCallback, useState } from 'react';
|
|
5
|
+
import { useCallback, useState, useSyncExternalStore } from 'react';
|
|
6
6
|
import Loading from '../../../primitives/loading.js';
|
|
7
7
|
import { useSidebar } from '../../../primitives/sidebar.js';
|
|
8
8
|
import { useConnectDesktopComputer, useOwnsPairedComputer, useThisComputerState, } from '../../../internal/tunnel/computer-connect.js';
|
|
9
9
|
import { useIsMobile } from '../../../internal/hooks/index.js';
|
|
10
10
|
import { useTranslations } from '../../../i18n/index.js';
|
|
11
11
|
export const COMPUTER_PROMO_DISMISSED_KEY = 'kortix.computer-promo.dismissed';
|
|
12
|
+
const subscribeNoop = () => () => { };
|
|
12
13
|
function readDismissed() {
|
|
13
14
|
try {
|
|
14
15
|
return window.localStorage.getItem(COMPUTER_PROMO_DISMISSED_KEY) === '1';
|
|
@@ -30,9 +31,10 @@ function readDismissed() {
|
|
|
30
31
|
* and closing the sheet unmounts it together with anything it renders.
|
|
31
32
|
*/
|
|
32
33
|
export function ProjectComputerNavItem({ projectId, onOpenConnect, }) {
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const [
|
|
34
|
+
// The server and the hydrating render read "dismissed"; the stored flag applies after.
|
|
35
|
+
const stored = useSyncExternalStore(subscribeNoop, readDismissed, () => true);
|
|
36
|
+
const [dismissedNow, setDismissed] = useState(false);
|
|
37
|
+
const dismissed = stored || dismissedNow;
|
|
36
38
|
const dismiss = useCallback(() => {
|
|
37
39
|
setDismissed(true);
|
|
38
40
|
try {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The Goals row shows only with the `goals` flag on. */
|
|
2
|
+
export declare function useGoalsRowVisible(projectId: string | undefined): boolean;
|
|
3
|
+
/** The Goals row, with the count of goals that wait on a person or need attention. */
|
|
4
|
+
export declare function ProjectGoalsNavItem({ projectId }: {
|
|
5
|
+
projectId: string;
|
|
6
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { uiRoutes } from '../../../lib/routes.js';
|
|
4
|
+
import { ProjectNavRow } from '../../../internal/layout/project-nav-row.js';
|
|
5
|
+
import { CountBadge } from '../../../primitives/count-badge.js';
|
|
6
|
+
import { useTranslations } from '../../../i18n/index.js';
|
|
7
|
+
import { useFeatureFlag, useProjectGoals } from '@kortix/sdk/react';
|
|
8
|
+
import { TargetIcon } from '@phosphor-icons/react';
|
|
9
|
+
import { useUiPathname } from '../../../providers/ui-provider.js';
|
|
10
|
+
import { goalNeedsYou } from '../../../internal/goals/goal-meta.js';
|
|
11
|
+
/** The Goals row shows only with the `goals` flag on. */
|
|
12
|
+
export function useGoalsRowVisible(projectId) {
|
|
13
|
+
return useFeatureFlag(projectId, 'goals').enabled;
|
|
14
|
+
}
|
|
15
|
+
/** The Goals row, with the count of goals that wait on a person or need attention. */
|
|
16
|
+
export function ProjectGoalsNavItem({ projectId }) {
|
|
17
|
+
const t = useTranslations('sidebar');
|
|
18
|
+
const pathname = useUiPathname();
|
|
19
|
+
const visible = useGoalsRowVisible(projectId);
|
|
20
|
+
const goals = useProjectGoals(visible ? projectId : null);
|
|
21
|
+
const needsYou = (goals.data ?? []).filter(goalNeedsYou).length;
|
|
22
|
+
const href = projectId ? uiRoutes().goals(projectId) : null;
|
|
23
|
+
if (!href || !visible)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsxs(ProjectNavRow, { active: pathname?.startsWith(href) === true, label: t('goals'), href: href, prefetch: true, children: [_jsx("span", { className: "shrink-0", children: _jsx(TargetIcon, {}) }), t('goals'), needsYou > 0 ? (_jsx("span", { className: "ml-auto flex shrink-0 items-center gap-1", children: _jsx(CountBadge, { count: needsYou, tone: "waiting" }) })) : null] }));
|
|
26
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { PromoNavRow } from './promo-nav-row.js';
|
|
4
|
-
import { useCallback, useState } from 'react';
|
|
4
|
+
import { useCallback, useState, useSyncExternalStore } from 'react';
|
|
5
5
|
import { useSidebar } from '../../../primitives/sidebar.js';
|
|
6
6
|
import { Mcp } from '../../../internal/icons/mcp.js';
|
|
7
7
|
import { useIsMobile } from '../../../internal/hooks/index.js';
|
|
8
8
|
import { useTranslations } from '../../../i18n/index.js';
|
|
9
9
|
export const MCP_PROMO_DISMISSED_KEY = 'kortix.mcp-promo.dismissed';
|
|
10
|
+
const subscribeNoop = () => () => { };
|
|
10
11
|
function readDismissed() {
|
|
11
12
|
try {
|
|
12
13
|
return window.localStorage.getItem(MCP_PROMO_DISMISSED_KEY) === '1';
|
|
@@ -26,7 +27,10 @@ export function ProjectMcpNavItem({ onOpenConnect }) {
|
|
|
26
27
|
const tComputers = useTranslations('computers');
|
|
27
28
|
const isMobile = useIsMobile();
|
|
28
29
|
const { setOpenMobile } = useSidebar();
|
|
29
|
-
|
|
30
|
+
// The server and the hydrating render read "dismissed"; the stored flag applies after.
|
|
31
|
+
const stored = useSyncExternalStore(subscribeNoop, readDismissed, () => true);
|
|
32
|
+
const [dismissedNow, setDismissed] = useState(false);
|
|
33
|
+
const dismissed = stored || dismissedNow;
|
|
30
34
|
const dismiss = useCallback(() => {
|
|
31
35
|
setDismissed(true);
|
|
32
36
|
try {
|
|
@@ -5,15 +5,20 @@ import { Input } from '../../../primitives/input.js';
|
|
|
5
5
|
import Loading from '../../../primitives/loading.js';
|
|
6
6
|
import { Modal, ModalBody, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, } from '../../../primitives/modal.js';
|
|
7
7
|
import { useTranslations } from '../../../i18n/index.js';
|
|
8
|
-
import {
|
|
8
|
+
import { useState } from 'react';
|
|
9
9
|
import { MAX_SESSION_NAME_LENGTH, useRenameSession } from '../../../session/actions/use-rename-session.js';
|
|
10
10
|
export function RenameSessionModal({ projectId, sessionId, currentName, open, onOpenChange, onSaved, }) {
|
|
11
11
|
const tI18nHardcoded = useTranslations('hardcodedUi');
|
|
12
12
|
const [value, setValue] = useState(currentName ?? '');
|
|
13
|
-
|
|
13
|
+
const [syncedFor, setSyncedFor] = useState([
|
|
14
|
+
open,
|
|
15
|
+
currentName,
|
|
16
|
+
]);
|
|
17
|
+
if (syncedFor[0] !== open || syncedFor[1] !== currentName) {
|
|
18
|
+
setSyncedFor([open, currentName]);
|
|
14
19
|
if (open)
|
|
15
20
|
setValue(currentName ?? '');
|
|
16
|
-
}
|
|
21
|
+
}
|
|
17
22
|
const renameMutation = useRenameSession(projectId, sessionId, {
|
|
18
23
|
onSuccess: () => {
|
|
19
24
|
onSaved?.();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The iframe snippet a host page pastes: the ACTUAL session page
|
|
3
|
+
* (`/projects/:projectId/sessions/:sessionId`), not a separate widget or
|
|
4
|
+
* viewer. Pure, so the test can pin the exact HTML without a browser.
|
|
5
|
+
*
|
|
6
|
+
* The snippet is static HTML by construction: the URL is built from ids, and
|
|
7
|
+
* the `title` attribute is a fixed string. Never interpolate a session title
|
|
8
|
+
* into the attribute — an unescaped quote would break out of it on the HOST
|
|
9
|
+
* page, which does not share this app's CSP.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sessionEmbedSnippet(projectId: string, sessionId: string, origin: string): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* "Embed" in the Share dialog: one copy button for the iframe snippet above.
|
|
14
|
+
* Framing the session page is allowed by the CSP `frame-ancestors` rule on
|
|
15
|
+
* that route (next.config.ts); the viewer inside the frame is the app's own
|
|
16
|
+
* auth. Same-site embeds require an existing signed-in session; cross-site
|
|
17
|
+
* embeds cannot sign in inside the frame (SameSite=Lax and /auth's CSP).
|
|
18
|
+
*
|
|
19
|
+
* Mounted inside `ModalContent`, so `window` is read at render time of an open
|
|
20
|
+
* dialog, never during SSR.
|
|
21
|
+
*/
|
|
22
|
+
export declare function SessionEmbedSection({ projectId, sessionId, }: {
|
|
23
|
+
projectId: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from '../../../primitives/button.js';
|
|
4
|
+
import { Label } from '../../../primitives/label.js';
|
|
5
|
+
import { errorToast, successToast } from '../../../primitives/toast.js';
|
|
6
|
+
import { useTranslations } from '../../../i18n/index.js';
|
|
7
|
+
import { CheckIcon, CodeIcon } from '@phosphor-icons/react';
|
|
8
|
+
import { useEffect, useRef, useState } from 'react';
|
|
9
|
+
/**
|
|
10
|
+
* The iframe snippet a host page pastes: the ACTUAL session page
|
|
11
|
+
* (`/projects/:projectId/sessions/:sessionId`), not a separate widget or
|
|
12
|
+
* viewer. Pure, so the test can pin the exact HTML without a browser.
|
|
13
|
+
*
|
|
14
|
+
* The snippet is static HTML by construction: the URL is built from ids, and
|
|
15
|
+
* the `title` attribute is a fixed string. Never interpolate a session title
|
|
16
|
+
* into the attribute — an unescaped quote would break out of it on the HOST
|
|
17
|
+
* page, which does not share this app's CSP.
|
|
18
|
+
*/
|
|
19
|
+
export function sessionEmbedSnippet(projectId, sessionId, origin) {
|
|
20
|
+
if (!projectId || !sessionId || !origin)
|
|
21
|
+
return null;
|
|
22
|
+
const src = `${origin}/projects/${encodeURIComponent(projectId)}/sessions/${encodeURIComponent(sessionId)}`;
|
|
23
|
+
return `<iframe src="${src}" style="height:640px;width:100%;border:0;border-radius:12px" title="Kortix session" loading="lazy"></iframe>`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* "Embed" in the Share dialog: one copy button for the iframe snippet above.
|
|
27
|
+
* Framing the session page is allowed by the CSP `frame-ancestors` rule on
|
|
28
|
+
* that route (next.config.ts); the viewer inside the frame is the app's own
|
|
29
|
+
* auth. Same-site embeds require an existing signed-in session; cross-site
|
|
30
|
+
* embeds cannot sign in inside the frame (SameSite=Lax and /auth's CSP).
|
|
31
|
+
*
|
|
32
|
+
* Mounted inside `ModalContent`, so `window` is read at render time of an open
|
|
33
|
+
* dialog, never during SSR.
|
|
34
|
+
*/
|
|
35
|
+
export function SessionEmbedSection({ projectId, sessionId, }) {
|
|
36
|
+
const t = useTranslations('hardcodedUi.sessionEmbed');
|
|
37
|
+
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
38
|
+
const [copied, setCopied] = useState(false);
|
|
39
|
+
const copiedTimer = useRef(null);
|
|
40
|
+
const mounted = useRef(false);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
mounted.current = true;
|
|
43
|
+
return () => {
|
|
44
|
+
mounted.current = false;
|
|
45
|
+
if (copiedTimer.current)
|
|
46
|
+
clearTimeout(copiedTimer.current);
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
const origin = typeof window === 'undefined' ? null : window.location.origin;
|
|
50
|
+
const snippet = origin ? sessionEmbedSnippet(projectId, sessionId, origin) : null;
|
|
51
|
+
const copy = async () => {
|
|
52
|
+
if (!snippet)
|
|
53
|
+
return;
|
|
54
|
+
try {
|
|
55
|
+
await navigator.clipboard.writeText(snippet);
|
|
56
|
+
if (!mounted.current)
|
|
57
|
+
return;
|
|
58
|
+
setCopied(true);
|
|
59
|
+
successToast(t('copied'));
|
|
60
|
+
if (copiedTimer.current)
|
|
61
|
+
clearTimeout(copiedTimer.current);
|
|
62
|
+
copiedTimer.current = setTimeout(() => setCopied(false), 2000);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
if (!mounted.current)
|
|
66
|
+
return;
|
|
67
|
+
errorToast(tI18nComplete.raw('text167c96824acb'));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
return (_jsxs("section", { className: "space-y-2", "data-testid": "session-embed", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: t('sectionTitle') }), _jsx("p", { className: "text-muted-foreground text-xs", children: t('sectionDescription') })] }), _jsxs(Button, { variant: "outline", size: "sm", disabled: !snippet, onClick: copy, children: [copied ? (_jsx(CheckIcon, { className: "text-kortix-green size-4 shrink-0" })) : (_jsx(CodeIcon, { className: "size-4 shrink-0" })), t('copy')] })] }));
|
|
71
|
+
}
|
|
@@ -13,7 +13,7 @@ import { sessionOversightQueryKey } from '../../../access/iam/session-oversight-
|
|
|
13
13
|
import { ShieldCheckIcon } from '@phosphor-icons/react';
|
|
14
14
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
15
15
|
import { useEffect, useState } from 'react';
|
|
16
|
-
import {
|
|
16
|
+
import { SessionEmbedSection } from './session-embed-section.js';
|
|
17
17
|
import { sessionAccessSummary, sessionAccessView } from './share-session-access.js';
|
|
18
18
|
import { refreshAfterShare } from './share-session-cache.js';
|
|
19
19
|
/**
|
|
@@ -105,5 +105,5 @@ export function ShareSessionModal({ projectId, session, open, onOpenChange, onSa
|
|
|
105
105
|
// Read-only, not hidden: a person a session was shared with should
|
|
106
106
|
// still be able to see who else is in it. Withholding that is what
|
|
107
107
|
// sent people to the editable dialog in the first place.
|
|
108
|
-
_jsx("p", { className: "text-foreground text-sm", "data-testid": "session-access-summary", children: session ? sessionAccessSummary(session) : null })), oversightOn ? (_jsx(InfoBanner, { tone: "neutral", icon: ShieldCheckIcon, "data-testid": "session-oversight-disclosure", children: tOversight.raw('shareDisclosure') })) : null, view.canEdit && session && publicSharesEnabled ? (_jsx(
|
|
108
|
+
_jsx("p", { className: "text-foreground text-sm", "data-testid": "session-access-summary", children: session ? sessionAccessSummary(session) : null })), oversightOn ? (_jsx(InfoBanner, { tone: "neutral", icon: ShieldCheckIcon, "data-testid": "session-oversight-disclosure", children: tOversight.raw('shareDisclosure') })) : null, view.canEdit && session && publicSharesEnabled ? (_jsx(SessionEmbedSection, { projectId: projectId, sessionId: session.session_id })) : null] }), _jsxs(ModalFooter, { className: "sm:justify-between", children: [_jsx(Button, { variant: "outline-ghost", size: "sm", className: "w-full sm:w-auto", onClick: () => onOpenChange(false), disabled: save.isPending, children: view.canEdit ? 'Cancel' : 'Close' }), view.canEdit && (_jsxs(Button, { size: "sm", onClick: () => save.mutate(), disabled: save.isPending || !isSharingComplete(sharing) || blockedSelection, className: "w-full sm:w-auto", children: [save.isPending && _jsx(Loading, {}), tI18nHardcoded.raw('i18nComplete.text1509f561f241')] }))] })] }) }));
|
|
109
109
|
}
|
|
@@ -3,11 +3,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { uiRoutes } from '../../lib/routes.js';
|
|
4
4
|
import { capabilityTabHref } from '../../internal/lib/capability-tab-routes.js';
|
|
5
5
|
import { useAppsRowVisible } from './footer/project-apps-nav.js';
|
|
6
|
+
import { useGoalsRowVisible } from './footer/project-goals-nav.js';
|
|
6
7
|
import { useRoomsRowVisible } from './footer/project-rooms-nav.js';
|
|
7
8
|
import { filesRow } from '../../internal/layout/files-nav-label.js';
|
|
8
9
|
import { CustomizeGlyph, useProjectNavRows, } from '../../internal/layout/project-settings-nav.js';
|
|
9
10
|
import { useTranslations } from '../../i18n/index.js';
|
|
10
|
-
import { AppWindowIcon, ChatsTeardropIcon, FoldersIcon, HardDrivesIcon } from '@phosphor-icons/react';
|
|
11
|
+
import { AppWindowIcon, ChatsTeardropIcon, FoldersIcon, HardDrivesIcon, TargetIcon } from '@phosphor-icons/react';
|
|
11
12
|
import { useMemo } from 'react';
|
|
12
13
|
/**
|
|
13
14
|
* Label, glyph, destination and availability of every customizable nav row,
|
|
@@ -21,12 +22,14 @@ export function useNavRowMeta(projectId) {
|
|
|
21
22
|
const { customize: customizeTab, files: filesTarget } = useProjectNavRows(projectId);
|
|
22
23
|
const appsVisible = useAppsRowVisible(projectId);
|
|
23
24
|
const roomsVisible = useRoomsRowVisible(projectId);
|
|
25
|
+
const goalsVisible = useGoalsRowVisible(projectId);
|
|
24
26
|
// Memoized on the gates, not rebuilt per render: the Customize sidebar
|
|
25
27
|
// modal memoizes its rows on this object, and a fresh one per render would
|
|
26
28
|
// re-render every row on every drag frame.
|
|
27
29
|
return useMemo(() => {
|
|
28
30
|
const appsHref = uiRoutes().apps(projectId);
|
|
29
31
|
const roomsHref = uiRoutes().rooms(projectId);
|
|
32
|
+
const goalsHref = uiRoutes().goals(projectId);
|
|
30
33
|
const files = filesTarget ? filesRow(projectId, filesTarget) : null;
|
|
31
34
|
return {
|
|
32
35
|
...(customizeTab
|
|
@@ -56,6 +59,15 @@ export function useNavRowMeta(projectId) {
|
|
|
56
59
|
},
|
|
57
60
|
}
|
|
58
61
|
: {}),
|
|
62
|
+
...(goalsVisible && goalsHref
|
|
63
|
+
? {
|
|
64
|
+
goals: {
|
|
65
|
+
label: t('goals'),
|
|
66
|
+
icon: _jsx(TargetIcon, {}),
|
|
67
|
+
href: goalsHref,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
: {}),
|
|
59
71
|
...(files
|
|
60
72
|
? {
|
|
61
73
|
files: {
|
|
@@ -66,5 +78,5 @@ export function useNavRowMeta(projectId) {
|
|
|
66
78
|
}
|
|
67
79
|
: {}),
|
|
68
80
|
};
|
|
69
|
-
}, [t, tI18nComplete, projectId, customizeTab, appsVisible, roomsVisible, filesTarget]);
|
|
81
|
+
}, [t, tI18nComplete, projectId, customizeTab, appsVisible, roomsVisible, goalsVisible, filesTarget]);
|
|
70
82
|
}
|