From 7a34a50bec9936d851925de4c30a367ecaa3a77c Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 10 Nov 2026 17:02:24 -0800 Subject: [PATCH] feat(cloud): asleep Files tab shows the files changed at sleep, read-only, with Wake; neutral note when nothing was saved (RA-10) --- .../offline/CloudAsleepFilesPanel.tsx | 109 ++++++++++++++++++ .../offline/use-asleep-cloud-workspace-id.ts | 12 ++ .../tree/FileExplorerFilesTreePane.tsx | 7 ++ src/renderer/src/i18n/locales/en.json | 8 +- 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/renderer/src/components/orca-cloud/offline/CloudAsleepFilesPanel.tsx create mode 100644 src/renderer/src/components/orca-cloud/offline/use-asleep-cloud-workspace-id.ts diff --git a/src/renderer/src/components/orca-cloud/offline/CloudAsleepFilesPanel.tsx b/src/renderer/src/components/orca-cloud/offline/CloudAsleepFilesPanel.tsx new file mode 100644 index 00000000000..2c39dc33f2f --- /dev/null +++ b/src/renderer/src/components/orca-cloud/offline/CloudAsleepFilesPanel.tsx @@ -0,0 +1,109 @@ +import React from 'react' +import { Play } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { useNow } from '@/hooks/use-now' +import { useTranslate } from '@/i18n/use-translate' +import { formatElapsedTime } from '@/components/worktree-creation/worktree-creation-clock' +import { CloudGitChanges } from '../CloudGitChanges' +import { wakeCloudWorkspace } from '../cloud-workspace-actions' +import { useCloudOfflineData } from './use-cloud-offline-data' +import type { CloudOfflineSnapshot } from '../../../../../shared/hosted-sandbox/cloud-workspace-offline-api' + +/** + * The Files tab of an Orca Cloud workspace that is asleep (RA-10). There is no file tree without + * the VM, so instead of a read error and Retry: the files that had changed when it went to sleep + * (from its read snapshot or this computer's cache), read-only and labeled as such, then Wake for + * the full view. With no snapshot, a neutral note and Wake. + */ +export function CloudAsleepFilesPanel({ workspaceId }: { workspaceId: string }): React.JSX.Element { + const t = useTranslate() + const { snapshot } = useCloudOfflineData(workspaceId, true) + const now = useNow(30_000) + const git = snapshot?.git ?? null + const wake = ( + + ) + return ( +
+ {git ? ( + <> + +
+

+ {t( + 'orcaCloudWorkspace.files.wakeForAll', + 'Wake the workspace to browse all its files.' + )} +

+ {wake} +
+ + ) : ( +
+

+ {t( + 'orcaCloudWorkspace.files.asleep', + 'Files aren’t available while the workspace is asleep.' + )} +

+ {wake} +
+ )} +
+ ) +} + +function SavedChanges({ + git, + at, + now +}: { + git: NonNullable + at: number | null + now: number +}): React.JSX.Element { + const t = useTranslate() + return ( +
+

+ {at + ? t( + 'orcaCloudWorkspace.files.changedAtSleep', + 'Changed files when this workspace went to sleep · {{time}}', + { + time: t('orcaCloudWorkspace.ago', '{{ago}} ago', { + ago: formatElapsedTime(Math.max(0, now - at)) + }) + } + ) + : t( + 'orcaCloudWorkspace.files.changedAtSleepNoTime', + 'Changed files when this workspace went to sleep' + )} +

+ ({ + sha: commit.id, + subject: commit.subject, + at: commit.at + })) + }} + /> +
+ ) +} diff --git a/src/renderer/src/components/orca-cloud/offline/use-asleep-cloud-workspace-id.ts b/src/renderer/src/components/orca-cloud/offline/use-asleep-cloud-workspace-id.ts new file mode 100644 index 00000000000..cd2adcef631 --- /dev/null +++ b/src/renderer/src/components/orca-cloud/offline/use-asleep-cloud-workspace-id.ts @@ -0,0 +1,12 @@ +import { useAppStore } from '@/store' +import { useCloudWorkspaceView } from '../use-cloud-workspace-view' + +/** The active workspace's id when it is an Orca Cloud workspace that is asleep (or failed to wake). */ +export function useAsleepCloudWorkspaceId(): string | null { + const activeWorktreeId = useAppStore((state) => state.activeWorktreeId) + const view = useCloudWorkspaceView(activeWorktreeId ?? '') + if (!activeWorktreeId || !view.isCloud) { + return null + } + return view.state === 'sleeping' || view.state === 'wakeFailed' ? activeWorktreeId : null +} diff --git a/src/renderer/src/components/right-sidebar/file-explorer/tree/FileExplorerFilesTreePane.tsx b/src/renderer/src/components/right-sidebar/file-explorer/tree/FileExplorerFilesTreePane.tsx index a9a3a43d6fe..8b2b1f426d5 100644 --- a/src/renderer/src/components/right-sidebar/file-explorer/tree/FileExplorerFilesTreePane.tsx +++ b/src/renderer/src/components/right-sidebar/file-explorer/tree/FileExplorerFilesTreePane.tsx @@ -9,6 +9,8 @@ import type { RuntimeFileListState } from '@/components/quick-open-file-list' import type { Repo } from '../../../../../../shared/repo-types' import type { RightSidebarExplorerView } from '../../../../../../shared/ui-chrome-types' import { FileExplorerTreeStatus } from './FileExplorerTreeStatus' +import { CloudAsleepFilesPanel } from '@/components/orca-cloud/offline/CloudAsleepFilesPanel' +import { useAsleepCloudWorkspaceId } from '@/components/orca-cloud/offline/use-asleep-cloud-workspace-id' import { FileExplorerVirtualRows } from '../panel/FileExplorerVirtualRows' import { canShowAddAsProjectAction } from '../navigation/file-explorer-add-project-action' import type { FileExplorerNameFilterProjectionSource } from './file-explorer-name-filter-projection' @@ -132,6 +134,11 @@ export function FileExplorerFilesTreePane({ hasNameFilter && !nameFilterFiles.loadError ? t('auto.components.right.sidebar.FileExplorer.2f4483d6c4', 'No files match this filter') : undefined + // An asleep Orca Cloud workspace has no tree to read: its saved changes and Wake, not an error. + const asleepCloudId = useAsleepCloudWorkspaceId() + if (asleepCloudId) { + return + } return (