@kortix/ui 0.14.6 → 0.14.8
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 +490 -382
- package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
- package/dist/access/access/access-dialog-share.js +4 -7
- package/dist/access/access/access-dialog.js +2 -1
- package/dist/access/access/principal-picker.js +6 -5
- package/dist/customize/agents/agent-authority-card.js +1 -2
- package/dist/customize/agents/agent-cover-card.d.ts +17 -0
- package/dist/customize/agents/agent-cover-card.js +13 -0
- 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 +68 -70
- package/dist/customize/agents/agent-page.js +27 -19
- 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 +43 -49
- 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/detail/app-page.js +19 -5
- 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/migrate-to-v2/migration-prompt.d.ts +1 -0
- package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
- 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/channels-view.js +2 -6
- package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
- 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/tabs/general-tab.d.ts +9 -2
- package/dist/customize/settings/tabs/general-tab.js +33 -11
- 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/new-entity-menu.js +4 -1
- package/dist/customize/skills/skills-page.js +4 -5
- package/dist/customize/triggers/schedule/then-fields.js +1 -5
- package/dist/customize/triggers/schedule-view.js +4 -2
- package/dist/files/project/api/runtime-files.js +3 -2
- package/dist/files/project/file-source.js +18 -1
- package/dist/files/project/hooks/use-file-content.js +2 -0
- 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 +12 -2
- package/dist/files/viewer/file-source.d.ts +3 -1
- package/dist/files/viewer/use-file-content-state.js +26 -7
- package/dist/files/viewer/viewer-actions.js +2 -2
- package/dist/i18n/en.d.ts +393 -12
- package/dist/i18n/en.js +400 -19
- package/dist/internal/components/agent-mark.d.ts +38 -0
- package/dist/internal/components/agent-mark.js +38 -0
- 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/lib/pixel-mark.d.ts +33 -0
- package/dist/internal/lib/pixel-mark.js +152 -0
- package/dist/internal/lib/query-client-singleton.d.ts +9 -3
- package/dist/internal/lib/query-client-singleton.js +18 -12
- package/dist/internal/lib/use-project-feature-flags.js +3 -3
- 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/genui/components/inline.d.ts +1 -1
- package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
- package/dist/markdown/setup-links/app-credentials-form.js +115 -0
- package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
- package/dist/markdown/setup-links/secret-intake-form.js +1 -1
- package/dist/primitives/entity-avatar.d.ts +4 -1
- package/dist/primitives/entity-avatar.js +4 -2
- package/dist/primitives/settings-section-header.d.ts +1 -1
- package/dist/primitives/user-avatar.d.ts +3 -1
- package/dist/primitives/user-avatar.js +4 -2
- package/dist/providers/ui-provider.d.ts +1 -1
- package/dist/session/composer/agent-selector.d.ts +7 -1
- package/dist/session/composer/agent-selector.js +20 -10
- 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-draft-recovery.d.ts +1 -2
- package/dist/session/composer/composer-draft-recovery.js +0 -13
- 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 +8 -6
- package/dist/session/composer/context-usage.js +9 -7
- 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/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/token-progress.d.ts +1 -1
- package/dist/session/composer/token-progress.js +6 -6
- package/dist/session/header/export-transcript-modal.d.ts +3 -2
- package/dist/session/header/export-transcript-modal.js +6 -3
- 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/models/model-selector.d.ts +3 -1
- package/dist/session/models/model-selector.js +3 -2
- package/dist/session/panel/easy/plan-card.d.ts +2 -0
- package/dist/session/panel/easy/plan-card.js +2 -0
- 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/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/plan-surface.d.ts +1 -0
- package/dist/session/thread/plan-surface.js +1 -0
- package/dist/session/thread/session-chat.js +5 -12
- package/dist/session/thread/session-context-metrics.d.ts +15 -0
- package/dist/session/thread/session-context-metrics.js +10 -1
- package/dist/session/thread/session-context-modal.d.ts +4 -2
- package/dist/session/thread/session-context-modal.js +16 -9
- package/dist/session/thread/session-history-scroll.d.ts +2 -1
- package/dist/session/thread/session-thread.js +1 -1
- package/dist/session/thread/transcript.d.ts +4 -0
- package/dist/session/thread/transcript.js +18 -5
- package/dist/session/thread/turn-share.d.ts +16 -0
- package/dist/session/thread/turn-share.js +162 -0
- package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
- package/dist/session/tool/tools/apply-patch-tool.js +1 -0
- package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-kill-tool.js +1 -0
- package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
- package/dist/session/tool/tools/pty-read-tool.js +2 -0
- package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
- package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-write-tool.js +1 -0
- package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/todo-write-tool.js +1 -0
- package/dist/session/turn/message-author-label.js +6 -2
- package/dist/session/turn/plan-anchor.d.ts +3 -0
- package/dist/session/turn/plan-anchor.js +3 -0
- package/dist/session/turn/plan-card.d.ts +7 -0
- package/dist/session/turn/plan-card.js +7 -0
- package/dist/styles.css +1 -1
- package/dist/terminal/pty-connection.d.ts +5 -4
- package/dist/terminal/session-terminal-connect-bar.js +2 -1
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/index.js +1 -0
- 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 +4 -5
- 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/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 +33 -9
- package/package.json +12 -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
|
@@ -84,7 +84,9 @@ export function TriggerToolbarMenu({ canManage, paused, isPending, onToggle, onS
|
|
|
84
84
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
85
85
|
if (!canManage)
|
|
86
86
|
return null;
|
|
87
|
-
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-base",
|
|
87
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-base",
|
|
88
|
+
// `size-9`: the New trigger button and the search input beside it are `h-9`.
|
|
89
|
+
className: "size-9", disabled: isPending, "aria-label": tI18nComplete.raw('textd47d7cb0e4f8'), children: _jsx(DotsThreeIcon, { className: "text-muted-foreground size-4 shrink-0" }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "w-76", children: [_jsx(DropdownMenuItem, { className: RICH_ROW_CLASS, disabled: paused, onSelect: () => onToggle(true), children: _jsx(RichRowBody, { icon: PauseIcon, title: tI18nComplete.raw('text0b6258b50010'), description: tI18nComplete.raw('textdae75dce541f') }) }), _jsx(DropdownMenuItem, { className: RICH_ROW_CLASS, disabled: !paused, onSelect: () => onToggle(false), children: _jsx(RichRowBody, { icon: PlayIcon, title: tI18nComplete.raw('text43e673c5503d'), description: tI18nComplete.raw('textced57bebd466') }) }), onSelectMode ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onSelect: onSelectMode, children: [_jsx(CheckSquareIcon, {}), tI18nComplete.raw('text2a78025de6aa')] })] })) : null] })] }));
|
|
88
90
|
}
|
|
89
91
|
/**
|
|
90
92
|
* The project-wide pause switch (`PATCH /projects/:id/triggers/activation`):
|
|
@@ -387,7 +389,7 @@ export function ScheduleView({ projectId }) {
|
|
|
387
389
|
Both are hidden until the list has loaded — neither means anything
|
|
388
390
|
on an error or a 403. */
|
|
389
391
|
showContent ? (
|
|
390
|
-
// Search, then one group: ⋯ and New trigger, both `secondary` at `h-
|
|
392
|
+
// Search, then one group: ⋯ and New trigger, both `secondary` at `h-9`, the search input's height.
|
|
391
393
|
_jsxs(ButtonGroup, { children: [triggers.length > 0 || paused ? (_jsx(TriggerActivationMenu, { projectId: projectId, onSelectMode: triggers.length > 0 ? enterSelectMode : undefined })) : null, canWrite ? (_jsx(NewEntityMenu, { label: copy.createLabel, pending: configure.pending, onChat: () => configure.start(newConfigPrompt('trigger')), manual: { onSelect: () => openCreate() }, caret: false })) : null] })) : undefined, children: [_jsxs("div", { className: "space-y-4", children: [accountPaused && showContent && (_jsxs(Alert, { variant: "warning", children: [_jsx(AlertTitle, { children: tAutomations('bannerTitle') }), _jsx(AlertDescription, { children: tAutomations('bannerDescription') })] })), paused && showContent && (_jsxs(Alert, { variant: "warning", children: [_jsx(AlertTitle, { children: tI18nComplete.raw('textb18b93a52cd2') }), _jsx(AlertDescription, { children: tI18nComplete.raw('textc9e9d972eda2') }), controls.canUpdate ? (_jsx(AlertActions, { children: _jsxs(Button, { variant: "outline", size: "sm", disabled: resumeAll.isPending, onClick: () => resumeAll.mutate(false), children: [resumeAll.isPending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, tI18nComplete.raw('text43e673c5503d')] }) })) : null] })), triggersQuery.isLoading ? (_jsx("div", { className: "space-y-1", children: Array.from({ length: 5 }).map((_, i) => (
|
|
392
394
|
// biome-ignore lint/suspicious/noArrayIndexKey: fixed-length placeholder
|
|
393
395
|
_jsx(Skeleton, { className: "h-10 rounded-md" }, i))) })) : isForbidden ? (_jsxs(Alert, { variant: "warning", children: [_jsx(AlertTitle, { children: tI18nComplete.raw('textabb84be05cfa') }), _jsxs(AlertDescription, { children: [tI18nComplete.raw('text22cd1f210415'), " ", copy.noun, tI18nComplete.raw('text382584f2456e')] })] })) : triggersQuery.isError ? (_jsx(ErrorState, { size: "sm", title: tI18nComplete('text7ce02a51e3dd', { value0: copy.noun }), description: triggersQuery.error?.message ?? tI18nComplete.raw('text0c953ab32c60'), action: _jsx(Button, { variant: "outline", size: "sm", onClick: () => triggersQuery.refetch(), children: tI18nComplete.raw('textd8b8392e2c54') }) })) : filter !== 'all' && ofKind.length === 0 ? (_jsx(EmptyState, { size: "sm", title: kindCopy[filter].emptyTitle })) : triggers.length === 0 ? (_jsx(EmptyState, { icon: LightningIcon, title: copy.emptyTitle, description: copy.emptyBody, className: "py-12", action: canWrite ? (_jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", onClick: () => openCreate(), children: [_jsx(PlusIcon, { className: "size-4 shrink-0" }), copy.createLabel] })) : undefined })) : filtered.length === 0 ? (_jsxs("p", { className: "text-muted-foreground px-3 py-6 text-center text-xs", children: [tI18nComplete.raw('text965b516df3ba'), ' ', _jsx("span", { className: "text-foreground font-medium", children: debouncedQuery }), "."] })) : (_jsx(TriggerList, { triggers: filtered, groups: appGroups, eventNames: eventNames, projectPaused: paused, selectedSlug: selected?.slug ?? null, runningSlug: run.isPending ? (run.variables?.slug ?? null) : liveSlug, onOpen: (t) => openTrigger(t.slug), selection: selectMode
|
|
@@ -194,12 +194,13 @@ export async function readFile(projectId, ref, filePath) {
|
|
|
194
194
|
// text, and the JSON content read is byte-safe for it.
|
|
195
195
|
if (hasExtension && getLanguageFromExt(name) !== 'plaintext') {
|
|
196
196
|
const result = await readProjectFile(projectId, relativePath, ref);
|
|
197
|
-
return { type: 'text', content: result.content };
|
|
197
|
+
return { type: 'text', content: result.content, revision: result.revision };
|
|
198
198
|
}
|
|
199
199
|
// Unknown or missing extension: the bytes decide, text or binary.
|
|
200
200
|
const bytes = await readRawBytes(projectId, relativePath, ref);
|
|
201
201
|
if (isTextBytes(bytes)) {
|
|
202
|
-
|
|
202
|
+
const result = await readProjectFile(projectId, relativePath, ref);
|
|
203
|
+
return { type: 'text', content: result.content, revision: result.revision, mimeType: 'text/plain; charset=utf-8' };
|
|
203
204
|
}
|
|
204
205
|
return { type: 'binary', content: '', mimeType: 'application/octet-stream' };
|
|
205
206
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { writeProjectFile } from '@kortix/sdk';
|
|
4
|
+
import { fileContentKeys } from './hooks/use-file-content.js';
|
|
5
|
+
import { useProjectCan } from '../../internal/lib/use-project-can.js';
|
|
6
|
+
import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
|
|
2
7
|
import { useMemo } from 'react';
|
|
3
8
|
import { downloadFile, uploadFile } from './api/runtime-files.js';
|
|
4
9
|
import { FilePathBreadcrumbs } from './components/file-breadcrumbs.js';
|
|
@@ -16,6 +21,8 @@ import { useBinaryBlob } from './hooks/use-binary-blob.js';
|
|
|
16
21
|
*/
|
|
17
22
|
export function useProjectFileSource() {
|
|
18
23
|
const ctx = useProjectContext();
|
|
24
|
+
const queryClient = useQueryClient();
|
|
25
|
+
const canWrite = useProjectCan(ctx?.projectId ?? '', PROJECT_ACTIONS.PROJECT_FILE_WRITE).allowed === true;
|
|
19
26
|
return useMemo(() => ({
|
|
20
27
|
id: 'project-ref',
|
|
21
28
|
useFileContent,
|
|
@@ -25,6 +32,16 @@ export function useProjectFileSource() {
|
|
|
25
32
|
? downloadFile(ctx.projectId, ctx.ref, filePath, fileName)
|
|
26
33
|
: Promise.reject(new Error('No project context for download')),
|
|
27
34
|
upload: uploadFile,
|
|
35
|
+
editablePreview: canWrite,
|
|
36
|
+
save: ctx && canWrite ? async (filePath, file, revision) => {
|
|
37
|
+
if (!revision)
|
|
38
|
+
throw new Error('Reload the file before saving.');
|
|
39
|
+
await writeProjectFile(ctx.projectId, {
|
|
40
|
+
path: filePath.replace(/^\/workspace\//, '').replace(/^\//, ''), ref: ctx.ref,
|
|
41
|
+
content: await file.text(), expected_revision: revision,
|
|
42
|
+
});
|
|
43
|
+
await queryClient.invalidateQueries({ queryKey: fileContentKeys.all });
|
|
44
|
+
} : undefined,
|
|
28
45
|
Breadcrumbs: FilePathBreadcrumbs,
|
|
29
|
-
}), [ctx]);
|
|
46
|
+
}), [ctx, canWrite, queryClient]);
|
|
30
47
|
}
|
|
@@ -20,6 +20,8 @@ export function useFileContent(filePath, options) {
|
|
|
20
20
|
staleTime: options?.staleTime ?? 10_000,
|
|
21
21
|
gcTime: 5 * 60_000,
|
|
22
22
|
refetchOnWindowFocus: false,
|
|
23
|
+
// A revision used for edits must remain paired with the displayed text.
|
|
24
|
+
refetchOnMount: false,
|
|
23
25
|
retry: (failureCount, error) => {
|
|
24
26
|
const msg = error.message.toLowerCase();
|
|
25
27
|
if (msg.includes('404') ||
|
|
@@ -9,7 +9,7 @@ import { cn } from '../../lib/utils.js';
|
|
|
9
9
|
import { WarningIcon as AlertTriangle, BracketsCurlyIcon as Braces, CheckIcon as Check, WarningCircleIcon as CircleAlert, CodeIcon as Code, EyeIcon as Eye, GlobeIcon as Globe, ArrowCounterClockwiseIcon as RotateCcw, FloppyDiskIcon as Save } from '@phosphor-icons/react';
|
|
10
10
|
import { Download } from '../../internal/icons/download.js';
|
|
11
11
|
export function FileHeader({ showHeader, Breadcrumbs, filePath, readOnly, hasUnsavedChanges, saveFlash, fileDiagErrorCount, fileDiagWarningCount, fileContent, isSaving, handleSave, latestContentRef, discardLabel, handleDiscard, isHtmlFile, isHtmlPreview, setIsHtmlPreview, isJsonFile, isJsonTreeView, setIsJsonTreeView, hasPreviewToggle, isMarkdownPreview, setIsMarkdownPreview, headerActions, handleDownload, blobUrl, rawBlob, tI18nHardcoded, tHardcodedUi }) {
|
|
12
|
-
return (_jsx(_Fragment, { children: showHeader && (_jsxs("div", { className: "border-border/50 flex h-10 shrink-0 items-center gap-2 border-b px-3 py-1.5", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1", children: [Breadcrumbs && _jsx(Breadcrumbs, { filePath: filePath }), !readOnly && hasUnsavedChanges && (_jsxs(Badge, { variant: "warning", size: "sm", className: "shrink-0", children: [_jsx(StatusDot, { tone: "warning", pulse: true }), tI18nHardcoded.raw('i18nComplete.text7117f0807129')] })), !readOnly && saveFlash && !hasUnsavedChanges && (_jsxs(Badge, { variant: "success", size: "sm", className: "shrink-0", children: [_jsx(Check, { className: "h-3 w-3" }), tI18nHardcoded.raw('i18nComplete.textb5c120b316c2')] })), readOnly && (_jsx(Badge, { variant: "muted", size: "sm", className: "shrink-0 tracking-wider uppercase", children: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line554JsxTextViewOnly') })), (fileDiagErrorCount > 0 || fileDiagWarningCount > 0) && (_jsxs("span", { className: "inline-flex shrink-0 items-center gap-1.5", children: [fileDiagErrorCount > 0 && (_jsxs("span", { className: "text-destructive inline-flex items-center gap-0.5 text-xs font-medium tabular-nums", children: [_jsx(CircleAlert, { className: "h-3 w-3" }), fileDiagErrorCount] })), fileDiagWarningCount > 0 && (_jsxs("span", { className: "text-kortix-orange inline-flex items-center gap-0.5 text-xs font-medium tabular-nums", children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), fileDiagWarningCount] }))] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [!readOnly && hasUnsavedChanges && fileContent?.type === 'text' && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "default", size: "sm", className: "h-7 gap-1.5 px-3 text-xs font-medium", onClick: () => handleSave(latestContentRef.current), disabled: isSaving, title: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line586JsxAttrTitleSaveS'), children: [isSaving ? (_jsx(Loading, { className: "h-3.5 w-3.5" })) : (_jsx(Save, { className: "h-3.5 w-3.5" })), tI18nHardcoded.raw('i18nComplete.text1509f561f241')] }), _jsx(Hint, { label: discardLabel, side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": discardLabel, className: "text-muted-foreground hover:text-foreground h-7 w-7 active:scale-[0.96]", onClick: handleDiscard, children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }) })] })), isHtmlFile && (_jsx(Hint, { label: isHtmlPreview ? tI18nHardcoded.raw('i18nComplete.text6ee818aa2de3') : 'Preview', side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": isHtmlPreview ? tI18nHardcoded.raw('i18nComplete.text6ee818aa2de3') : 'Preview', "aria-pressed": isHtmlPreview, className: cn('h-7 w-7 active:scale-[0.96]', isHtmlPreview && 'text-primary'), onClick: () => setIsHtmlPreview((v) => !v), children: isHtmlPreview ? _jsx(Code, { className: "h-4 w-4" }) : _jsx(Globe, { className: "h-4 w-4" }) }) })), isJsonFile && fileContent?.type === 'text' && (_jsx(Hint, { label: isJsonTreeView
|
|
12
|
+
return (_jsx(_Fragment, { children: showHeader && (_jsxs("div", { className: "border-border/50 flex h-10 shrink-0 items-center gap-2 border-b px-3 py-1.5", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1", children: [Breadcrumbs && _jsx(Breadcrumbs, { filePath: filePath }), !readOnly && hasUnsavedChanges && (_jsxs(Badge, { variant: "warning", size: "sm", className: "shrink-0", children: [_jsx(StatusDot, { tone: "warning", pulse: true }), tI18nHardcoded.raw('i18nComplete.text7117f0807129')] })), !readOnly && saveFlash && !hasUnsavedChanges && (_jsxs(Badge, { variant: "success", size: "sm", className: "shrink-0", children: [_jsx(Check, { className: "h-3 w-3" }), tI18nHardcoded.raw('i18nComplete.textb5c120b316c2')] })), readOnly && (_jsx(Badge, { variant: "muted", size: "sm", className: "shrink-0 tracking-wider uppercase", children: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line554JsxTextViewOnly') })), (fileDiagErrorCount > 0 || fileDiagWarningCount > 0) && (_jsxs("span", { className: "inline-flex shrink-0 items-center gap-1.5", children: [fileDiagErrorCount > 0 && (_jsxs("span", { className: "text-destructive inline-flex items-center gap-0.5 text-xs font-medium tabular-nums", children: [_jsx(CircleAlert, { className: "h-3 w-3" }), fileDiagErrorCount] })), fileDiagWarningCount > 0 && (_jsxs("span", { className: "text-kortix-orange inline-flex items-center gap-0.5 text-xs font-medium tabular-nums", children: [_jsx(AlertTriangle, { className: "h-3 w-3" }), fileDiagWarningCount] }))] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [!readOnly && hasUnsavedChanges && fileContent?.type === 'text' && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "default", size: "sm", className: "h-7 gap-1.5 px-3 text-xs font-medium", onClick: () => { void handleSave(latestContentRef.current).catch(() => undefined); }, disabled: isSaving, title: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line586JsxAttrTitleSaveS'), children: [isSaving ? (_jsx(Loading, { className: "h-3.5 w-3.5" })) : (_jsx(Save, { className: "h-3.5 w-3.5" })), tI18nHardcoded.raw('i18nComplete.text1509f561f241')] }), _jsx(Hint, { label: discardLabel, side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": discardLabel, className: "text-muted-foreground hover:text-foreground h-7 w-7 active:scale-[0.96]", onClick: handleDiscard, children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }) })] })), isHtmlFile && (_jsx(Hint, { label: isHtmlPreview ? tI18nHardcoded.raw('i18nComplete.text6ee818aa2de3') : 'Preview', side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": isHtmlPreview ? tI18nHardcoded.raw('i18nComplete.text6ee818aa2de3') : 'Preview', "aria-pressed": isHtmlPreview, className: cn('h-7 w-7 active:scale-[0.96]', isHtmlPreview && 'text-primary'), onClick: () => setIsHtmlPreview((v) => !v), children: isHtmlPreview ? _jsx(Code, { className: "h-4 w-4" }) : _jsx(Globe, { className: "h-4 w-4" }) }) })), isJsonFile && fileContent?.type === 'text' && (_jsx(Hint, { label: isJsonTreeView
|
|
13
13
|
? tI18nHardcoded.raw('i18nComplete.text6ee818aa2de3')
|
|
14
14
|
: tI18nHardcoded.raw('i18nComplete.text4f50bda41e87'), side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": isJsonTreeView
|
|
15
15
|
? tI18nHardcoded.raw('i18nComplete.text6ee818aa2de3')
|
|
@@ -60,4 +60,4 @@ export interface FilePreviewModalProps extends FilePreviewState {
|
|
|
60
60
|
* hit that path, which is why only the mouse appeared broken.)
|
|
61
61
|
* Z-index is dialog-depth aware so it always stacks above its host overlay.
|
|
62
62
|
*/
|
|
63
|
-
export declare function FilePreviewModal({ selectedFilePath, panelMode, filePathList, currentFileIndex, onClose, onNext, onPrev, source, HistoryContent, statusSlot, extraActions, shareContext, historyLabel, embedded, targetLine, }: FilePreviewModalProps): import("react").JSX.Element | null;
|
|
63
|
+
export declare function FilePreviewModal({ selectedFilePath, panelMode, filePathList, currentFileIndex, onClose: closePreview, onNext: nextPreview, onPrev: prevPreview, source, HistoryContent, statusSlot, extraActions, shareContext, historyLabel, embedded, targetLine, }: FilePreviewModalProps): import("react").JSX.Element | null;
|
|
@@ -54,9 +54,19 @@ const NO_REFRESH = {
|
|
|
54
54
|
* hit that path, which is why only the mouse appeared broken.)
|
|
55
55
|
* Z-index is dialog-depth aware so it always stacks above its host overlay.
|
|
56
56
|
*/
|
|
57
|
-
export function FilePreviewModal({ selectedFilePath, panelMode, filePathList, currentFileIndex, onClose, onNext, onPrev, source, HistoryContent, statusSlot, extraActions, shareContext, historyLabel = "History", embedded = false, targetLine = null, }) {
|
|
57
|
+
export function FilePreviewModal({ selectedFilePath, panelMode, filePathList, currentFileIndex, onClose: closePreview, onNext: nextPreview, onPrev: prevPreview, source, HistoryContent, statusSlot, extraActions, shareContext, historyLabel = "History", embedded = false, targetLine = null, }) {
|
|
58
58
|
const tI18nHardcoded = useTranslations("hardcodedUi");
|
|
59
59
|
const isOpen = panelMode === "viewer" && !!selectedFilePath;
|
|
60
|
+
const [dirty, setDirty] = useState(false);
|
|
61
|
+
const leave = (action) => {
|
|
62
|
+
if (dirty && !window.confirm('Discard unsaved changes?'))
|
|
63
|
+
return;
|
|
64
|
+
setDirty(false);
|
|
65
|
+
action();
|
|
66
|
+
};
|
|
67
|
+
const onClose = () => leave(closePreview);
|
|
68
|
+
const onNext = () => leave(nextPreview);
|
|
69
|
+
const onPrev = () => leave(prevPreview);
|
|
60
70
|
// Embedded viewers start inline (in the side panel); "Expand" pops them to
|
|
61
71
|
// the full-screen overlay. Non-embedded viewers are always full-screen.
|
|
62
72
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -264,7 +274,7 @@ export function FilePreviewModal({ selectedFilePath, panelMode, filePathList, cu
|
|
|
264
274
|
: "Expand", side: "bottom", children: _jsx(Button, { variant: "ghost", size: "icon", "aria-label": expanded
|
|
265
275
|
? tI18nHardcoded.raw("i18nComplete.textde12e3c7c7a8")
|
|
266
276
|
: "Expand", "aria-pressed": expanded, className: "text-muted-foreground hover:text-foreground h-8 w-8 active:scale-[0.96]", onClick: () => setExpanded((v) => !v), children: expanded ? (_jsx(Minimize2, { className: "h-4 w-4" })) : (_jsx(Maximize2, { className: "h-4 w-4" })) }) })), _jsx(ButtonGroup, { className: "ml-1.5", children: _jsx(Hint, { label: tI18nHardcoded.raw("autoFeaturesFileViewerFilePreviewModalJsxAttrTitleClosea40d0510"), side: "bottom", children: _jsx(Button, { variant: "secondary", size: "icon-base", "aria-label": tI18nHardcoded.raw("autoFeaturesFileViewerFilePreviewModalJsxAttrTitleClosea40d0510"), className: "active:scale-[0.96]", onClick: onClose, children: _jsx(X, { className: "h-4 w-4" }) }) }) })] })] }));
|
|
267
|
-
const body = (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-full w-full overflow-hidden px-3 pt-3 pb-2", children: _jsx(FileSourceProvider, { value: source, children: _jsx(FileContentRenderer, { filePath: selectedFilePath, reloadKey: reloadKey, showHeader:
|
|
277
|
+
const body = (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-full w-full overflow-hidden px-3 pt-3 pb-2", children: _jsx(FileSourceProvider, { value: source, children: _jsx(FileContentRenderer, { filePath: selectedFilePath, reloadKey: reloadKey, onUnsavedChange: setDirty, showHeader: !!source.editablePreview, readOnly: !source.editablePreview, targetLine: targetLine, markdownPreview: markdownPreview, onMarkdownPreviewChange: setMarkdownPreview }) }) }), historyPath && (_jsx("div", { className: "bg-popover border-border/60 animate-in slide-in-from-bottom-4 fade-in-0 absolute right-4 bottom-4 z-30 overflow-hidden rounded-md border shadow-md duration-normal", children: _jsx(HistoryContent, { filePath: historyPath, onClose: () => setHistoryPath(null) }) }))] }));
|
|
268
278
|
const panelInner = (_jsxs(_Fragment, { children: [_jsx("div", { ref: toolbarRef, className: "border-border/40 bg-background/95 flex h-12 shrink-0 items-center gap-2 border-b px-3 backdrop-blur-sm", children: toolbar }), _jsx("div", { ref: contentRef, className: "relative min-h-0 flex-1 overflow-hidden", children: body })] }));
|
|
269
279
|
// Inline (embedded, not expanded): fill the relative parent — the side panel —
|
|
270
280
|
// instead of portaling to a full-screen overlay. No backdrop, no body lock.
|
|
@@ -5,6 +5,7 @@ import { type ComponentType, type ReactNode } from 'react';
|
|
|
5
5
|
* their own typed hooks without conversion.
|
|
6
6
|
*/
|
|
7
7
|
export interface FileContent {
|
|
8
|
+
revision?: string;
|
|
8
9
|
type: 'text' | 'binary';
|
|
9
10
|
content: string;
|
|
10
11
|
patch?: FilePatch;
|
|
@@ -79,7 +80,8 @@ export interface FileSource {
|
|
|
79
80
|
* instead of `upload`, so a source can refuse a save over a file someone
|
|
80
81
|
* changed since it was read (a conditional write) rather than clobber it.
|
|
81
82
|
*/
|
|
82
|
-
|
|
83
|
+
editablePreview?: boolean;
|
|
84
|
+
save?: (filePath: string, file: File, revision?: string) => Promise<unknown>;
|
|
83
85
|
/**
|
|
84
86
|
* Every byte the viewer shows comes from this source, which owns the
|
|
85
87
|
* authorization. Previewers that otherwise read a sandbox path themselves
|
|
@@ -61,6 +61,13 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
61
61
|
const contentRevision = useContentRevision(fileContent);
|
|
62
62
|
const fetchRevision = useContentRevision(dataUpdatedAt || undefined);
|
|
63
63
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
64
|
+
// A refetch may replace server content while CodeEditor retains a dirty draft.
|
|
65
|
+
// CAS must compare against the revision that draft started from.
|
|
66
|
+
const draftRevisionRef = useRef(fileContent?.revision);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!hasUnsavedChanges)
|
|
69
|
+
draftRevisionRef.current = fileContent?.revision;
|
|
70
|
+
}, [hasUnsavedChanges, fileContent?.revision]);
|
|
64
71
|
const [isSaving, setIsSaving] = useState(false);
|
|
65
72
|
const [saveFlash, setSaveFlash] = useState(false);
|
|
66
73
|
// Tracks the latest editor content so we can save from the header button.
|
|
@@ -120,12 +127,12 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
120
127
|
// HEIC conversion — converts the raw HEIC blob to a renderable JPEG URL
|
|
121
128
|
const { url: heicImageUrl, isConverting: heicConverting } = useHeicBlob(isHeicImage ? rawBlob : null, fileName);
|
|
122
129
|
const displayContent = fileContent?.content ?? '';
|
|
123
|
-
//
|
|
130
|
+
// Refresh clean content only; header Save must keep the visible dirty draft.
|
|
124
131
|
useEffect(() => {
|
|
125
|
-
if (
|
|
126
|
-
latestContentRef.current = fileContent
|
|
132
|
+
if (!hasUnsavedChanges) {
|
|
133
|
+
latestContentRef.current = fileContent?.content ?? '';
|
|
127
134
|
}
|
|
128
|
-
}, [fileContent?.content]);
|
|
135
|
+
}, [fileContent?.content, hasUnsavedChanges]);
|
|
129
136
|
// Reset state when the FILE changes — markdown defaults to rendered preview.
|
|
130
137
|
//
|
|
131
138
|
// `setIsMarkdownPreview` MUST NOT be a dependency here, and this effect must
|
|
@@ -167,15 +174,17 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
167
174
|
// Save handler — called by CodeEditor (Cmd+S) and by the header Save button.
|
|
168
175
|
// When called from the header button we pass latestContentRef.current.
|
|
169
176
|
// When called from CodeEditor's Cmd+S, CodeEditor passes its own localContent.
|
|
177
|
+
const draftGeneration = useRef(0);
|
|
170
178
|
const handleSave = useCallback(async (content) => {
|
|
171
179
|
if (readOnly)
|
|
172
180
|
return;
|
|
181
|
+
const generation = draftGeneration.current;
|
|
173
182
|
setIsSaving(true);
|
|
174
183
|
try {
|
|
175
184
|
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
|
176
185
|
const file = new File([blob], fileName, { type: 'text/plain' });
|
|
177
186
|
if (source.save) {
|
|
178
|
-
await source.save(filePath, file);
|
|
187
|
+
await source.save(filePath, file, draftRevisionRef.current);
|
|
179
188
|
}
|
|
180
189
|
else {
|
|
181
190
|
const parentPath = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
@@ -184,7 +193,15 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
184
193
|
// Refetch so fileContent.content (= originalContent for CodeEditor) updates.
|
|
185
194
|
// CodeEditor's originalContent effect will then sync savedContent.current
|
|
186
195
|
// to match localContent, clearing its internal hasChanges flag.
|
|
187
|
-
await refetch();
|
|
196
|
+
const refreshed = await refetch();
|
|
197
|
+
if (refreshed && typeof refreshed === 'object' && 'data' in refreshed) {
|
|
198
|
+
const data = refreshed.data;
|
|
199
|
+
if (data && typeof data === 'object' && 'revision' in data && typeof data.revision === 'string') {
|
|
200
|
+
draftRevisionRef.current = data.revision;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (draftGeneration.current !== generation)
|
|
204
|
+
return;
|
|
188
205
|
setHasUnsavedChanges(false);
|
|
189
206
|
setSaveFlash(true);
|
|
190
207
|
if (saveFlashTimerRef.current)
|
|
@@ -199,6 +216,7 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
199
216
|
? err.message
|
|
200
217
|
: tI18nHardcoded.raw('i18nComplete.text27c2ccd962c2'),
|
|
201
218
|
}));
|
|
219
|
+
throw err;
|
|
202
220
|
}
|
|
203
221
|
finally {
|
|
204
222
|
setIsSaving(false);
|
|
@@ -216,6 +234,7 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
216
234
|
const handleEditorChange = useCallback((content) => {
|
|
217
235
|
if (readOnly)
|
|
218
236
|
return;
|
|
237
|
+
draftGeneration.current++;
|
|
219
238
|
latestContentRef.current = content;
|
|
220
239
|
}, [readOnly]);
|
|
221
240
|
// Cmd+S handler for when CodeEditor is not mounted (e.g. markdown preview)
|
|
@@ -226,7 +245,7 @@ export function useFileContentState({ filePath, onUnsavedChange, onSaved, readOn
|
|
|
226
245
|
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
227
246
|
e.preventDefault();
|
|
228
247
|
if (hasUnsavedChanges && latestContentRef.current) {
|
|
229
|
-
handleSave(latestContentRef.current);
|
|
248
|
+
void handleSave(latestContentRef.current).catch(() => undefined);
|
|
230
249
|
}
|
|
231
250
|
}
|
|
232
251
|
};
|
|
@@ -148,8 +148,8 @@ function expiryLabel(expiresAt) {
|
|
|
148
148
|
/**
|
|
149
149
|
* `Copy link` and everything about the link behind it, in one popover.
|
|
150
150
|
*
|
|
151
|
-
* A file or app mint always makes a NEW public token
|
|
152
|
-
*
|
|
151
|
+
* A file or app mint always makes a NEW public token, so the popover reads
|
|
152
|
+
* the session's live shares first:
|
|
153
153
|
*
|
|
154
154
|
* - a live link for this exact file or port → show it, copy it, see when it
|
|
155
155
|
* expires, revoke it;
|