-
Notifications
You must be signed in to change notification settings - Fork 960
revert: v2 workspace loading screen animation (#3788) #3820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ import { useQuery } from "@tanstack/react-query"; | |
| import { createFileRoute, useNavigate } from "@tanstack/react-router"; | ||
| import { useCallback, useEffect, useRef, useState } from "react"; | ||
| import { GoGitBranch } from "react-icons/go"; | ||
| import { HiExclamationTriangle } from "react-icons/hi2"; | ||
| import { HiCheck, HiExclamationTriangle } from "react-icons/hi2"; | ||
| import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; | ||
| import { authClient } from "renderer/lib/auth-client"; | ||
| import { electronTrpc } from "renderer/lib/electron-trpc"; | ||
|
|
@@ -15,15 +15,13 @@ import { | |
| clearAttachments, | ||
| loadAttachments, | ||
| } from "renderer/lib/pending-attachment-store"; | ||
| import { V2WorkspaceLoadingView } from "renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView"; | ||
| import { useAdoptWorktree } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useAdoptWorktree"; | ||
| import { useCheckoutDashboardWorkspace } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCheckoutDashboardWorkspace"; | ||
| import { useCreateDashboardWorkspace } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace"; | ||
| import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; | ||
| import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; | ||
| import type { PendingWorkspaceRow } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema"; | ||
| import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; | ||
| import type { WorkspaceInitStep } from "shared/types/workspace-init"; | ||
| import type { ResolvedPrContent } from "./buildForkAgentLaunch"; | ||
| import { | ||
| buildAdoptPayload, | ||
|
|
@@ -279,8 +277,7 @@ function PendingWorkspacePage() { | |
| }, [pending, fireIntent]); | ||
|
|
||
| // Poll host-service for step-by-step progress (fork + checkout only; | ||
| // adopt is fast and doesn't instrument progress). Drives the keypad-loader | ||
| // off real backend state so keys press as steps advance. | ||
| // adopt is fast and doesn't instrument progress). | ||
| const intentHasProgress = | ||
| pending?.intent === "fork" || pending?.intent === "checkout"; | ||
| const hostUrl = useHostTargetUrl(pending?.hostTarget ?? null); | ||
|
|
@@ -298,7 +295,7 @@ function PendingWorkspacePage() { | |
| enabled: pending?.status === "creating" && !!hostUrl && intentHasProgress, | ||
| }); | ||
|
|
||
| const loaderStep = mapHostProgressToInitStep(progress?.steps); | ||
| const steps = progress?.steps ?? []; | ||
|
|
||
| const STALE_THRESHOLD_MS = 2 * 60 * 1000; | ||
| const [now, setNow] = useState(Date.now()); | ||
|
|
@@ -374,62 +371,12 @@ function PendingWorkspacePage() { | |
| ); | ||
| } | ||
|
|
||
| const creatingTitle = | ||
| const creatingLabel = | ||
| pending.intent === "adopt" | ||
| ? "Adopting worktree" | ||
| ? "Adopting worktree..." | ||
| : pending.intent === "checkout" | ||
| ? "Checking out branch" | ||
| : "Setting up workspace"; | ||
|
|
||
| // Render the keypad through the "succeeded" hold (and during the brief | ||
| // pre-sync window before that) so the last key animates to pressed — | ||
| // the host clears progress without ever flagging "registering: done", | ||
| // so the success transition is the only signal we have for that frame. | ||
| const showKeypad = | ||
| pending.status === "creating" || | ||
| (pending.status === "succeeded" && !(syncTimedOut && !workspaceSynced)); | ||
|
|
||
| if (showKeypad) { | ||
| const isFinalizing = pending.status === "succeeded"; | ||
| return ( | ||
| <V2WorkspaceLoadingView | ||
| workspaceName={pending.name} | ||
| title={creatingTitle} | ||
| currentStep={isFinalizing ? "ready" : loaderStep} | ||
| description={ | ||
| isFinalizing | ||
| ? "Workspace ready — opening..." | ||
| : isStale | ||
| ? "This is taking longer than expected..." | ||
| : `Takes 10s to a few minutes (started ${elapsedLabel})` | ||
| } | ||
| > | ||
| {!isFinalizing && ( | ||
| <button | ||
| type="button" | ||
| className="mt-2 rounded-md border px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-accent" | ||
| onClick={() => { | ||
| collections.pendingWorkspaces.delete(pendingId); | ||
| void clearAttachments(pendingId); | ||
| void navigate({ to: "/" }); | ||
| }} | ||
| > | ||
| Dismiss | ||
| </button> | ||
| )} | ||
| {isFinalizing && pending.warnings.length > 0 && ( | ||
| <ul className="mt-2 space-y-1 text-xs text-amber-500 text-left"> | ||
| {pending.warnings.map((w) => ( | ||
| <li key={w} className="flex items-start gap-1.5"> | ||
| <HiExclamationTriangle className="size-3.5 mt-0.5 shrink-0" /> | ||
| <span>{w}</span> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| )} | ||
| </V2WorkspaceLoadingView> | ||
| ); | ||
| } | ||
| ? "Checking out branch..." | ||
| : "Creating workspace..."; | ||
|
|
||
| return ( | ||
| <div className="flex h-full w-full flex-1 justify-center pt-24"> | ||
|
|
@@ -442,30 +389,59 @@ function PendingWorkspacePage() { | |
| </div> | ||
| </div> | ||
|
|
||
| {pending.status === "succeeded" && syncTimedOut && !workspaceSynced && ( | ||
| <div className="space-y-4"> | ||
| <div className="flex items-start gap-2 text-sm text-amber-500"> | ||
| <HiExclamationTriangle className="size-4 mt-0.5 shrink-0" /> | ||
| <span> | ||
| Workspace was created but hasn't synced to this device yet. | ||
| Check your connection. | ||
| {pending.status === "creating" && ( | ||
| <div className="space-y-3"> | ||
| <div className="flex items-center justify-between"> | ||
| <p | ||
| className={`text-sm ${isStale ? "text-amber-500" : "text-muted-foreground"}`} | ||
| > | ||
| {isStale | ||
| ? "This is taking longer than expected..." | ||
| : creatingLabel} | ||
| </p> | ||
| <span className="text-xs tabular-nums text-muted-foreground/50"> | ||
| {elapsedLabel} | ||
| </span> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <button | ||
| type="button" | ||
| className="rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90" | ||
| onClick={() => setSyncTimedOut(false)} | ||
| > | ||
| Keep waiting | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className="rounded-md border px-3 py-1.5 text-sm hover:bg-accent" | ||
| onClick={doNavigate} | ||
| > | ||
| Open anyway | ||
| </button> | ||
| {intentHasProgress && steps.length > 0 ? ( | ||
| <div className="space-y-2"> | ||
| {steps.map((step) => ( | ||
| <div | ||
| key={step.id} | ||
| className="flex items-center gap-2.5 text-sm" | ||
| > | ||
| {step.status === "done" ? ( | ||
| <HiCheck className="size-4 text-emerald-500" /> | ||
| ) : step.status === "active" ? ( | ||
| <div className="size-4 flex items-center justify-center"> | ||
| <div className="size-2.5 rounded-full bg-foreground animate-pulse" /> | ||
| </div> | ||
| ) : ( | ||
| <div className="size-4 flex items-center justify-center"> | ||
| <div className="size-2 rounded-full bg-muted-foreground/30" /> | ||
| </div> | ||
| )} | ||
| <span | ||
| className={ | ||
| step.status === "done" || step.status === "active" | ||
| ? "text-foreground" | ||
| : "text-muted-foreground/50" | ||
| } | ||
| > | ||
| {step.label} | ||
| </span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ) : ( | ||
| // Adopt has no host-side progress steps — show a generic spinner. | ||
| <div className="flex items-center gap-2.5 text-sm text-muted-foreground"> | ||
| <div className="size-4 flex items-center justify-center"> | ||
| <div className="size-2.5 rounded-full bg-foreground animate-pulse" /> | ||
| </div> | ||
| </div> | ||
| )} | ||
| <div className="flex gap-2 pt-1"> | ||
| <button | ||
| type="button" | ||
| className="rounded-md border px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-accent" | ||
|
|
@@ -481,6 +457,63 @@ function PendingWorkspacePage() { | |
| </div> | ||
| )} | ||
|
|
||
| {pending.status === "succeeded" && | ||
| (syncTimedOut && !workspaceSynced ? ( | ||
| <div className="space-y-4"> | ||
| <div className="flex items-start gap-2 text-sm text-amber-500"> | ||
| <HiExclamationTriangle className="size-4 mt-0.5 shrink-0" /> | ||
| <span> | ||
| Workspace was created but hasn't synced to this device yet. | ||
| Check your connection. | ||
| </span> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <button | ||
| type="button" | ||
| className="rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90" | ||
| onClick={() => setSyncTimedOut(false)} | ||
| > | ||
| Keep waiting | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className="rounded-md border px-3 py-1.5 text-sm hover:bg-accent" | ||
| onClick={doNavigate} | ||
| > | ||
| Open anyway | ||
|
Comment on lines
+478
to
+483
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This button is only rendered when |
||
| </button> | ||
| <button | ||
| type="button" | ||
| className="rounded-md border px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-accent" | ||
| onClick={() => { | ||
| collections.pendingWorkspaces.delete(pendingId); | ||
| void clearAttachments(pendingId); | ||
| void navigate({ to: "/" }); | ||
| }} | ||
| > | ||
| Dismiss | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <div className="space-y-2"> | ||
| <div className="flex items-center gap-2 text-sm text-emerald-500"> | ||
| <HiCheck className="size-4" /> | ||
| <span>Workspace ready — opening...</span> | ||
| </div> | ||
| {pending.warnings.length > 0 && ( | ||
| <ul className="space-y-1 text-xs text-amber-500"> | ||
| {pending.warnings.map((w) => ( | ||
| <li key={w} className="flex items-start gap-1.5"> | ||
| <HiExclamationTriangle className="size-3.5 mt-0.5 shrink-0" /> | ||
| <span>{w}</span> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| )} | ||
| </div> | ||
| ))} | ||
|
|
||
| {pending.status === "failed" && ( | ||
| <div className="space-y-4"> | ||
| <div className="flex items-start gap-2 text-sm text-destructive"> | ||
|
|
@@ -518,24 +551,3 @@ function PendingWorkspacePage() { | |
| </div> | ||
| ); | ||
| } | ||
|
|
||
| type HostProgressStep = { | ||
| id: string; | ||
| label: string; | ||
| status: "pending" | "active" | "done"; | ||
| }; | ||
|
|
||
| // Maps the host-service's 3-step progress (ensuring_repo → creating_worktree | ||
| // → registering) onto the v1 keypad's step vocabulary. Skipped keys (fetching, | ||
| // copying_config) press through quickly when the keypad jumps past them. | ||
| function mapHostProgressToInitStep( | ||
| steps: HostProgressStep[] | null | undefined, | ||
| ): WorkspaceInitStep | undefined { | ||
| if (!steps || steps.length === 0) return undefined; | ||
| const byId = new Map(steps.map((s) => [s.id, s.status])); | ||
| if (byId.get("registering") === "done") return "ready"; | ||
| if (byId.get("registering") === "active") return "finalizing"; | ||
| if (byId.get("creating_worktree") === "active") return "creating_worktree"; | ||
| if (byId.get("ensuring_repo") === "active") return "syncing"; | ||
| return "pending"; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include
pr-checkoutin the progress gate.pr-checkoutgoes throughcheckoutWorkspace()inuseFireIntent, but this condition excludes it, so that flow falls back to the generic spinner instead of showing host-service steps like normal checkout.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents