Skip to content

Migrate v1 keypad loading screen to v2 as a separate page#3788

Merged
AviPeltz merged 6 commits into
mainfrom
migrate-loading-screen-from-v1-to-v2-with-separate
Apr 27, 2026
Merged

Migrate v1 keypad loading screen to v2 as a separate page#3788
AviPeltz merged 6 commits into
mainfrom
migrate-loading-screen-from-v1-to-v2-with-separate

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Apr 27, 2026

Summary

  • Ports v1's keypad-loader / step-progress visual to v2 as a standalone component (V2WorkspaceLoadingView) with copies of KeypadLoader and StepProgress that drop the audio dep and accept a generic currentStep prop.
  • Adds a separate route /v2-workspace-loading/$workspaceId that the v2 workspace layout redirects to while the v2Workspaces collection is hydrating; the loading page navigates back once isReady flips true.
  • Wires V2WorkspaceLoadingView into the pending workspace page (/pending/$pendingId) for both creating and the brief succeeded window. Title varies by intent (Setting up workspace / Checking out branch / Adopting worktree); Dismiss button passes through as children during creation; warnings render below the keypad in the success frame.
  • Drives the keypad off real workspaceCreation.getProgress polling for fork + checkout (mapper collapses the host's 3 steps onto the keypad's 5-key vocabulary). On succeeded the loader switches to currentStep="ready" so the last key reaches the pressed state — the host clears progress before flagging registering: done, so this transition is the only signal we have for that frame. Navigation fires immediately after success.
  • Fallback timer for adopt + cold-load runs at 400ms/step so keys still animate quickly when no real progress is available.
  • Replaces the previous inline "Workspace ready — opening..." block; sync-timeout recovery UI and failure UI are unchanged.

Test plan

  • Create a new workspace via the v2 modal (fork): keypad keys press in step with host-service progress, title reads "Setting up workspace", dismiss returns to dashboard.
  • Create via checkout: same keypad behavior, title reads "Checking out branch".
  • Adopt an orphan worktree: title reads "Adopting worktree", fallback timer drives the keypad (no host-side progress), all keys cycle within ~2.5s.
  • Open an existing v2 workspace from a cold start: layout redirects to /v2-workspace-loading/$workspaceId, keypad shows briefly, page transitions back to the workspace once collections hydrate.
  • Stale state (>2min still creating) swaps the description to "This is taking longer than expected..."
  • Sync-timed-out (workspace created but Electric sync stalls >10s): keypad gives way to the existing recovery UI with Keep waiting / Open anyway / Dismiss.
  • Failed creates render the existing failure UI with retry / dismiss.
  • Workspace creation flows straight through to the actual workspace — no perceptible navigation hold.

Adds a separate /v2-workspace-loading/$workspaceId route that mirrors
v1's keypad-loader/step-progress visuals while the v2Workspaces
collection is hydrating. Layout redirects there when isReady is false
and the loading page navigates back to the workspace once data lands.
The pending workspace page now drives V2WorkspaceLoadingView off the
live workspaceCreation.getProgress feed (fork + checkout) so keys press
in response to real backend steps instead of a slow fixed timer.
Mapper collapses the host's 3 steps onto the keypad's 5-key vocabulary;
fallback timer for adopt + cold-load is sped up from 1500ms to 400ms.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

Pending/workspace flow refactor: pending page and workspace layout now route to and render a new V2WorkspaceLoadingView that maps host progress into init steps and displays a keypad loader + step progress until navigation to the workspace occurs.

Changes

Cohort / File(s) Summary
Pending page / routing
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx
Replaced prior step-list/spinner UI with V2WorkspaceLoadingView usage; added route for v2-workspace-loading and redirect from V2WorkspaceLayout when workspace query not ready; pending page maps host progress to init steps and defers navigation briefly after success.
V2WorkspaceLoadingView
.../V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx, .../V2WorkspaceLoadingView/index.ts
New view component that centers KeypadLoader and StepProgress, accepts optional currentStep/workspace metadata, and auto-advances visible steps when no external currentStep provided.
KeypadLoader
.../KeypadLoader/KeypadLoader.tsx, .../KeypadLoader/index.ts, .../KeypadLoader/KeypadLoader.css
New exported component + CSS implementing a 5-key keypad visual loader. Keys expose data-pressed/data-active driven by current init step; includes layered images and animations (respects prefers-reduced-motion).
StepProgress
.../StepProgress/StepProgress.tsx, .../StepProgress/index.ts, .../StepProgress/StepProgress.css
New exported component + CSS rendering ordered init steps (excluding ready), animating transitions, briefly holding just-completed steps, and showing icons/ellipsis for active progress.
Pending UI adjustments
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
Title text updated (no trailing ellipses) and warning/dismiss handling consolidated into V2WorkspaceLoadingView branch; removed prior per-step UI and HiCheck success icon usage.

Sequence Diagram

sequenceDiagram
    participant User
    participant Pending as Pending Page
    participant Layout as V2WorkspaceLayout
    participant V2Load as V2WorkspaceLoadingView
    participant Keypad as KeypadLoader
    participant Steps as StepProgress
    participant Host
    participant DB

    User->>Pending: open pending workspace
    Pending->>Layout: mounts workspace layout
    Layout->>V2Load: redirect to /v2-workspace-loading if workspace not ready
    Host->>DB: emit progress updates
    DB-->>V2Load: mapped currentStep updates
    V2Load->>Keypad: render with currentStep
    V2Load->>Steps: render with currentStep
    Keypad-->>User: visual keypad states (pressed/active)
    Steps-->>User: animated step list (hold/completion)
    DB-->>V2Load: final step = ready
    V2Load->>Pending: delay then navigate to workspace route
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I tap five keys with a twitch and a cheer,
Steps shimmer, hold, then whisper "near",
A bob, a glow, a final soft press—
The worktree wakes from loader's caress,
Hop-hop, we're ready, off we steer!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description covers objectives, testing scenarios, and implementation details comprehensively, but is missing several required template sections. Add the missing template sections: 'Type of Change' (mark the appropriate checkbox), and 'Related Issues' (link any GitHub issues). Optionally add 'Screenshots' if UI changes are visual.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main change: migrating v1's keypad loading screen to v2 as a separate page, which aligns with the core objective of porting the UI component and adding a new route.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate-loading-screen-from-v1-to-v2-with-separate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR ports the v1 keypad loading animation to v2 as a standalone V2WorkspaceLoadingView (with KeypadLoader and StepProgress), wires it into the pending workspace page for all three creation intents, and adds a /v2-workspace-loading/$workspaceId splash route the v2 workspace layout redirects to while the v2Workspaces collection hydrates on cold start.

  • Unconditional navigation in loading page: V2WorkspaceLoadingPage navigates to /v2-workspace/$workspaceId whenever isReady is true, even when workspace is null (row not found in the hydrated collection). A workspace !== null guard is needed so the loading screen holds until the record actually syncs.
  • Potential redirect loop on cold start: layout.tsx redirects to the loading page when !isReady; if useLiveQuery initialises isReady to false on every fresh mount before delivering the already-hydrated state, re-mounting the layout after the loading page navigates back triggers another redirect, creating a flicker loop between the two routes.

Confidence Score: 4/5

Safe to merge after the two P1 navigation issues are addressed; the visual components themselves are solid.

Two P1 logic issues exist: (1) the loading page navigates unconditionally when isReady regardless of whether the workspace row exists, and (2) the bidirectional redirect between the layout and loading page can loop if useLiveQuery doesn't initialise synchronously from the hydrated collection on re-mount. The keypad, step progress, and pending-page wiring are well-implemented.

v2-workspace-loading/$workspaceId/page.tsx (unconditional redirect) and v2-workspace/layout.tsx (redirect loop risk).

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx New cold-start loading route: navigates unconditionally when isReady even if the workspace row is null, and participates in a potential redirect loop with the v2-workspace layout.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx Adds redirect to loading route when !isReady; could create a flicker/redirect loop with the loading page if useLiveQuery always initialises isReady to false on re-mount.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx Replaces the inline creating-state UI with V2WorkspaceLoadingView; mapHostProgressToInitStep has a gap-state that returns "pending" between host steps, causing a brief visual regression.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx New container component; fallback timer and prop-driven step wiring look correct; no issues found.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.tsx New v2 copy of KeypadLoader without audio dependency; pressed/active derivation via getStepIndex is correct.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/StepProgress/StepProgress.tsx New step-progress ticker; one-at-a-time advancement with DONE_HOLD_MS hold and backward-jump guard looks correct; "failed" step returns index -1 but is never passed in practice.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant L as V2WorkspaceLayout
    participant LP as V2WorkspaceLoadingPage
    participant DB as useLiveQuery (v2Workspaces)

    U->>L: Navigate to /v2-workspace/$id (cold start)
    L->>DB: subscribe(v2Workspaces)
    DB-->>L: isReady=false
    L->>LP: navigate (replace) /v2-workspace-loading/$id
    LP->>DB: subscribe(v2Workspaces)
    DB-->>LP: isReady=false then isReady=true
    LP->>L: navigate (replace) /v2-workspace/$id
    L->>DB: subscribe(v2Workspaces) re-mount
    alt useLiveQuery returns isReady=true synchronously
        DB-->>L: isReady=true
        L->>U: Render workspace
    else useLiveQuery initialises isReady=false on every mount
        DB-->>L: isReady=false
        L->>LP: navigate (replace) loop
        LP->>LP: isReady=true immediately navigate back
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx
Line: 28-35

Comment:
**Unconditional navigation when `isReady` — workspace may not exist**

`useEffect` navigates to `/v2-workspace/$workspaceId` whenever `isReady` flips to `true`, even when `workspace` is `null` (the collection loaded but the row wasn't found). If a user somehow reaches this URL with a non-existent or already-deleted `workspaceId`, they'll be silently redirected to the workspace route, which renders `WorkspaceNotFoundState`. Adding a `workspace !== null` guard ensures the loading page only advances when the record actually hydrated — it can otherwise stay and keep showing the animation while the row hasn't synced yet.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx
Line: 74-82

Comment:
**Potential redirect loop on cold-start re-mount**

When the loading page's `isReady` fires and it navigates back to `/v2-workspace/$workspaceId` (line 30–34 of `page.tsx`), this layout re-mounts and immediately runs its own `useLiveQuery`. If `useLiveQuery` initialises `isReady` to `false` synchronously on every fresh mount (before the first subscription tick delivers the already-hydrated collection state), this effect fires again with `workspaceId && !isReady === true` and sends the user back to the loading page. The loading page sees `isReady=true` almost immediately and bounces back, creating a rapidly alternating flicker loop until the hook initialises to `true`. The guard `if (!workspaceId || !isReady) return null` on line 84 means the `Outlet` never renders during that window, which compounds the visible jank.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
Line: 517-527

Comment:
**Gap state between host steps maps to `"pending"`, causing keypad to visually reset**

When `ensuring_repo` transitions from `"active"``"done"` but `creating_worktree` has not yet become `"active"` (a window that can span the 500 ms poll interval), none of the four conditions match and the function falls through to `return "pending"`. This takes `StepProgress` back to index 0 — visible as the progress ticker snapping backwards — before jumping forward again when the next poll arrives. Adding a `byId.get("ensuring_repo") === "done"` case mapped to something like `"fetching"` (or `"creating_worktree"`) would eliminate the regression.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "wire keypad loader to host-service progr..." | Re-trigger Greptile

Comment on lines +28 to +35
useEffect(() => {
if (!isReady) return;
void navigate({
to: "/v2-workspace/$workspaceId",
params: { workspaceId },
replace: true,
});
}, [isReady, navigate, workspaceId]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unconditional navigation when isReady — workspace may not exist

useEffect navigates to /v2-workspace/$workspaceId whenever isReady flips to true, even when workspace is null (the collection loaded but the row wasn't found). If a user somehow reaches this URL with a non-existent or already-deleted workspaceId, they'll be silently redirected to the workspace route, which renders WorkspaceNotFoundState. Adding a workspace !== null guard ensures the loading page only advances when the record actually hydrated — it can otherwise stay and keep showing the animation while the row hasn't synced yet.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx
Line: 28-35

Comment:
**Unconditional navigation when `isReady` — workspace may not exist**

`useEffect` navigates to `/v2-workspace/$workspaceId` whenever `isReady` flips to `true`, even when `workspace` is `null` (the collection loaded but the row wasn't found). If a user somehow reaches this URL with a non-existent or already-deleted `workspaceId`, they'll be silently redirected to the workspace route, which renders `WorkspaceNotFoundState`. Adding a `workspace !== null` guard ensures the loading page only advances when the record actually hydrated — it can otherwise stay and keep showing the animation while the row hasn't synced yet.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +74 to +82
useEffect(() => {
if (workspaceId && !isReady) {
void navigate({
to: "/v2-workspace-loading/$workspaceId",
params: { workspaceId },
replace: true,
});
}
}, [workspaceId, isReady, navigate]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Potential redirect loop on cold-start re-mount

When the loading page's isReady fires and it navigates back to /v2-workspace/$workspaceId (line 30–34 of page.tsx), this layout re-mounts and immediately runs its own useLiveQuery. If useLiveQuery initialises isReady to false synchronously on every fresh mount (before the first subscription tick delivers the already-hydrated collection state), this effect fires again with workspaceId && !isReady === true and sends the user back to the loading page. The loading page sees isReady=true almost immediately and bounces back, creating a rapidly alternating flicker loop until the hook initialises to true. The guard if (!workspaceId || !isReady) return null on line 84 means the Outlet never renders during that window, which compounds the visible jank.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx
Line: 74-82

Comment:
**Potential redirect loop on cold-start re-mount**

When the loading page's `isReady` fires and it navigates back to `/v2-workspace/$workspaceId` (line 30–34 of `page.tsx`), this layout re-mounts and immediately runs its own `useLiveQuery`. If `useLiveQuery` initialises `isReady` to `false` synchronously on every fresh mount (before the first subscription tick delivers the already-hydrated collection state), this effect fires again with `workspaceId && !isReady === true` and sends the user back to the loading page. The loading page sees `isReady=true` almost immediately and bounces back, creating a rapidly alternating flicker loop until the hook initialises to `true`. The guard `if (!workspaceId || !isReady) return null` on line 84 means the `Outlet` never renders during that window, which compounds the visible jank.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +517 to +527
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";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Gap state between host steps maps to "pending", causing keypad to visually reset

When ensuring_repo transitions from "active""done" but creating_worktree has not yet become "active" (a window that can span the 500 ms poll interval), none of the four conditions match and the function falls through to return "pending". This takes StepProgress back to index 0 — visible as the progress ticker snapping backwards — before jumping forward again when the next poll arrives. Adding a byId.get("ensuring_repo") === "done" case mapped to something like "fetching" (or "creating_worktree") would eliminate the regression.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
Line: 517-527

Comment:
**Gap state between host steps maps to `"pending"`, causing keypad to visually reset**

When `ensuring_repo` transitions from `"active"``"done"` but `creating_worktree` has not yet become `"active"` (a window that can span the 500 ms poll interval), none of the four conditions match and the function falls through to `return "pending"`. This takes `StepProgress` back to index 0 — visible as the progress ticker snapping backwards — before jumping forward again when the next poll arrives. Adding a `byId.get("ensuring_repo") === "done"` case mapped to something like `"fetching"` (or `"creating_worktree"`) would eliminate the regression.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.tsx (1)

28-64: Pre-compute thresholdIdx once per key.

getStepIndex(pressedAfter) is recomputed on every render for all five keys. Since pressedAfter is static, you can resolve thresholds when defining KEYS and skip the per-render lookup.

♻️ Proposed refactor
-const KEYS: readonly KeyDef[] = [
+const KEYS_RAW: readonly KeyDef[] = [
 	{
 		id: "one",
 		pressedAfter: "verifying",
 		activeSteps: ["pending", "syncing", "verifying"],
 		Icon: LuRefreshCw,
 		label: "Syncing",
 	},
 	// ... rest unchanged
 ];
+
+const KEYS = KEYS_RAW.map((k) => ({
+	...k,
+	thresholdIdx: getStepIndex(k.pressedAfter),
+}));

And in the render:

-{KEYS.map(({ id, pressedAfter, activeSteps, Icon }) => {
-    const thresholdIdx = getStepIndex(pressedAfter);
+{KEYS.map(({ id, thresholdIdx, activeSteps, Icon }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.tsx
around lines 28 - 64, The KEYS array currently stores pressedAfter strings and
calls getStepIndex(pressedAfter) on every render; compute each key's threshold
index once when defining KEYS to avoid repeated lookups. Modify the KEYS
definition (KeyDef entries) to include a precomputed numeric field (e.g.,
thresholdIdx or thresholdIndex) computed from getStepIndex(pressedAfter) and
then update rendering logic to read that field instead of calling getStepIndex
for each key; update any references to pressedAfter in the render to use the new
threshold field so per-render work is eliminated.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx (1)

38-44: Optionally stop the interval once stepIdx reaches the end.

Once stepIdx is clamped to VISIBLE_STEPS.length - 1, the interval keeps firing every 400ms with a no-op setStepIdx. Not a leak (cleared on unmount), but easy to short-circuit.

♻️ Proposed refactor
 useEffect(() => {
 	if (currentStepProp !== undefined) return;
 	const id = window.setInterval(() => {
-		setStepIdx((prev) => Math.min(prev + 1, VISIBLE_STEPS.length - 1));
+		setStepIdx((prev) => {
+			const next = prev + 1;
+			if (next >= VISIBLE_STEPS.length - 1) {
+				window.clearInterval(id);
+				return VISIBLE_STEPS.length - 1;
+			}
+			return next;
+		});
 	}, STEP_INTERVAL_MS);
 	return () => window.clearInterval(id);
 }, [currentStepProp]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx
around lines 38 - 44, The interval keeps firing after stepIdx reaches
VISIBLE_STEPS.length - 1; update the useEffect (the hook that depends on
currentStepProp) so the interval clears itself once the last step is reached: in
the window.setInterval callback (used with STEP_INTERVAL_MS) compute the next
index from setStepIdx (using current prev), and if next === VISIBLE_STEPS.length
- 1 (or next === prev) call window.clearInterval(id) before returning, otherwise
call setStepIdx as today; keep the early return when currentStepProp !==
undefined. Reference: useEffect, currentStepProp, setStepIdx, VISIBLE_STEPS,
STEP_INTERVAL_MS.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/StepProgress/StepProgress.tsx (1)

97-184: Consider extracting icon sub-components.

CheckCircle, EmptyCircle, HalfCircle, and Ellipsis are exported-shape React components living alongside the main one. Per the repo convention (one component per file), you could move these into a co-located icons/ (or parts/) subfolder. Not blocking — they're private helpers — but worth tracking if these grow.

As per coding guidelines: "Do not create multi-component files; use one component per file."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/StepProgress/StepProgress.tsx
around lines 97 - 184, The file defines multiple React subcomponents
(CheckCircle, EmptyCircle, HalfCircle, Ellipsis) alongside the main
StepIcon/StepProgress component which violates the "one component per file"
convention; extract each of those four into its own file under a co-located
icons/ (or parts/) folder (e.g., icons/CheckCircle.tsx, icons/EmptyCircle.tsx,
icons/HalfCircle.tsx, icons/Ellipsis.tsx) as default-exported React components,
update the StepProgress/StepIcon file to import those components and remove
their local definitions, and ensure any relative imports/exports or tests
referencing these symbols are updated accordingly so behavior remains unchanged.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx (1)

517-527: Mapper can momentarily reverse progress between step transitions.

Between when one host step flips to done and the next step flips to active, all three mapped checks miss and the function returns "pending". That can briefly walk the keypad backward (e.g. creating_worktreependingfinalizing) at each step boundary. Including done fallthroughs anchors the loader to the highest reached step:

♻️ Proposed monotonic mapping
 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") === "done") return "finalizing";
 	if (byId.get("creating_worktree") === "active") return "creating_worktree";
+	if (byId.get("ensuring_repo") === "done") return "creating_worktree";
 	if (byId.get("ensuring_repo") === "active") return "syncing";
 	return "pending";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
around lines 517 - 527, The mapper mapHostProgressToInitStep currently returns
"pending" during the brief window between one step becoming "done" and the next
becoming "active", which causes UI regressions; update mapHostProgressToInitStep
(and its use of HostProgressStep/WorkspaceInitStep) to treat "done" as having
reached that step by adding fallthrough checks for done statuses (e.g., if
byId.get("creating_worktree") === "done' return "creating_worktree"; if
byId.get("ensuring_repo") === "done" return "syncing"; similarly ensure
registering "done" still returns "ready"), and order the checks so the
highest-reached step (active or done) is returned to keep progress monotonic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx:
- Line 17: V2WorkspaceLoadingView (and its dependencies KeypadLoader and
StepProgress) is being imported across a route segment ($workspaceId), which
couples component ownership to routing; move V2WorkspaceLoadingView plus
KeypadLoader and StepProgress into a shared, route-independent location (for
example _authenticated/_dashboard/components/V2WorkspaceLoadingView/) and update
both consumers (v2-workspace-loading/$workspaceId/page.tsx and
pending/$pendingId/page.tsx) to import from that new stable path so the
component is owned by a shared parent instead of a specific route segment.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.css:
- Around line 1-10: In the .keypad-loader rule update the formatting so there's
a blank line between the custom property --travel and the first regular
declaration; specifically insert an empty line before the position: relative;
declaration (in the .keypad-loader block) to satisfy the
declaration-empty-line-before Stylelint rule.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx:
- Around line 517-527: The mapper mapHostProgressToInitStep currently returns
"pending" during the brief window between one step becoming "done" and the next
becoming "active", which causes UI regressions; update mapHostProgressToInitStep
(and its use of HostProgressStep/WorkspaceInitStep) to treat "done" as having
reached that step by adding fallthrough checks for done statuses (e.g., if
byId.get("creating_worktree") === "done' return "creating_worktree"; if
byId.get("ensuring_repo") === "done" return "syncing"; similarly ensure
registering "done" still returns "ready"), and order the checks so the
highest-reached step (active or done) is returned to keep progress monotonic.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.tsx:
- Around line 28-64: The KEYS array currently stores pressedAfter strings and
calls getStepIndex(pressedAfter) on every render; compute each key's threshold
index once when defining KEYS to avoid repeated lookups. Modify the KEYS
definition (KeyDef entries) to include a precomputed numeric field (e.g.,
thresholdIdx or thresholdIndex) computed from getStepIndex(pressedAfter) and
then update rendering logic to read that field instead of calling getStepIndex
for each key; update any references to pressedAfter in the render to use the new
threshold field so per-render work is eliminated.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/StepProgress/StepProgress.tsx:
- Around line 97-184: The file defines multiple React subcomponents
(CheckCircle, EmptyCircle, HalfCircle, Ellipsis) alongside the main
StepIcon/StepProgress component which violates the "one component per file"
convention; extract each of those four into its own file under a co-located
icons/ (or parts/) folder (e.g., icons/CheckCircle.tsx, icons/EmptyCircle.tsx,
icons/HalfCircle.tsx, icons/Ellipsis.tsx) as default-exported React components,
update the StepProgress/StepIcon file to import those components and remove
their local definitions, and ensure any relative imports/exports or tests
referencing these symbols are updated accordingly so behavior remains unchanged.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx:
- Around line 38-44: The interval keeps firing after stepIdx reaches
VISIBLE_STEPS.length - 1; update the useEffect (the hook that depends on
currentStepProp) so the interval clears itself once the last step is reached: in
the window.setInterval callback (used with STEP_INTERVAL_MS) compute the next
index from setStepIdx (using current prev), and if next === VISIBLE_STEPS.length
- 1 (or next === prev) call window.clearInterval(id) before returning, otherwise
call setStepIdx as today; keep the early return when currentStepProp !==
undefined. Reference: useEffect, currentStepProp, setStepIdx, VISIBLE_STEPS,
STEP_INTERVAL_MS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c67c4fa-656d-4f1f-a26a-1080f572976a

📥 Commits

Reviewing files that changed from the base of the PR and between b9c0d7c and 0769027.

⛔ Files ignored due to path filters (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/assets/key-single.png is excluded by !**/*.png
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/assets/keypad-base.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.css
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/StepProgress/StepProgress.css
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/StepProgress/StepProgress.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/StepProgress/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx

clearAttachments,
loadAttachments,
} from "renderer/lib/pending-attachment-store";
import { V2WorkspaceLoadingView } from "renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Promote V2WorkspaceLoadingView to a shared location.

V2WorkspaceLoadingView is now consumed by both v2-workspace-loading/$workspaceId/page.tsx (where it currently lives) and this pending page in a sibling route. The cross-route import path crossing a $workspaceId segment is also a smell — that segment exists for routing, not for component ownership. Consider moving the component (and its KeypadLoader/StepProgress dependencies) up to the highest shared parent, e.g. _authenticated/_dashboard/components/V2WorkspaceLoadingView/, so both consumers import from a stable, route-independent location.

As per coding guidelines: "If a utility is used once, nest it under the parent's directory. If used 2+ times, promote it to the highest shared parent's directory or top-level components/."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
at line 17, V2WorkspaceLoadingView (and its dependencies KeypadLoader and
StepProgress) is being imported across a route segment ($workspaceId), which
couples component ownership to routing; move V2WorkspaceLoadingView plus
KeypadLoader and StepProgress into a shared, route-independent location (for
example _authenticated/_dashboard/components/V2WorkspaceLoadingView/) and update
both consumers (v2-workspace-loading/$workspaceId/page.tsx and
pending/$pendingId/page.tsx) to import from that new stable path so the
component is owned by a shared parent instead of a specific route segment.

Comment on lines +1 to +10
.keypad-loader {
--travel: 26;
position: relative;
aspect-ratio: 400 / 310;
display: flex;
place-items: center;
width: clamp(260px, 34vw, 420px);
transform-style: preserve-3d;
user-select: none;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stylelint: missing empty line before position declaration.

Static analysis flagged declaration-empty-line-before on line 3 — the rule wants a blank line between a custom property and a regular declaration in the same block.

🎨 Proposed fix
 .keypad-loader {
 	--travel: 26;
+
 	position: relative;
🧰 Tools
🪛 Stylelint (17.9.0)

[error] 3-3: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/`$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/KeypadLoader.css
around lines 1 - 10, In the .keypad-loader rule update the formatting so there's
a blank line between the custom property --travel and the first regular
declaration; specifically insert an empty line before the position: relative;
declaration (in the .keypad-loader block) to satisfy the
declaration-empty-line-before Stylelint rule.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 13 files

The host clears workspaceCreation progress without ever reporting
"registering: done", so the 5th keypad key never got the pressed state
from real progress alone — and the moment the pending row flipped to
"succeeded" we swapped to the old text-based "Workspace ready" UI.

Now V2WorkspaceLoadingView stays mounted through "succeeded" with
currentStep="ready" (all keys pressed), and doNavigate is held 700ms
so the press animation actually plays before the route transition.
The sync-timeout recovery UI still applies when the workspace row
hasn't synced; warnings render below the keypad in the success path.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx (2)

526-536: Mapper can transiently regress to "pending" between host steps.

The mapper only checks "active" for ensuring_repo / creating_worktree. If a step has finished but the next one hasn't flipped to active yet (e.g. creating_worktree === "done" while registering === "pending"), the function falls through to "pending" and the keypad visually rewinds to the first key. Same hazard between ensuring_repo === "done" and creating_worktree === "pending".

♻️ Make the mapping monotonic by checking `done` for prior steps
 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") === "done") return "finalizing";
 	if (byId.get("creating_worktree") === "active") return "creating_worktree";
+	if (byId.get("ensuring_repo") === "done") return "creating_worktree";
 	if (byId.get("ensuring_repo") === "active") return "syncing";
 	return "pending";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
around lines 526 - 536, mapHostProgressToInitStep can regress to "pending" when
a later step has finished but the prior hasn't flipped to "active"; fix by
making the mapping monotonic: in function mapHostProgressToInitStep (and using
HostProgressStep ids "registering", "creating_worktree", "ensuring_repo"), treat
"done" on later steps as evidence of progress — e.g. if
byId.get("creating_worktree") === "done" return "creating_worktree"; if
byId.get("ensuring_repo") === "done" return "syncing"; keep existing checks for
"active" and the registering/done => "ready" case so the UI never visually
rewinds to "pending" when a subsequent step is already done.

517-521: Derive HostProgressStep from the tRPC procedure return type to prevent type drift.

The local type duplicates the host-service ProgressStep interface. If the server adds a new status value (e.g., "error"), the client type won't update automatically, and the mapper's fallthrough to "pending" (line 535) would silently mask the new state. Use type inference instead: Awaited<ReturnType<typeof client.workspaceCreation.getProgress.query>>['steps'][number] or equivalent to keep the type in sync with the server.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
around lines 517 - 521, The local HostProgressStep type duplicates the server
ProgressStep and can drift; replace it by deriving the client-side step type
from the tRPC procedure return type (use Awaited<ReturnType<typeof
client.workspaceCreation.getProgress.query>>['steps'][number] or equivalent) so
the client stays in sync with the server, and remove the hardcoded union
("pending"|"active"|"done"); update any mappers that inspect step.status (the
mapper that currently falls back to "pending") to handle the derived type
instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx:
- Around line 415-424: The current UI only renders pending.warnings when
isFinalizing is true inside V2WorkspaceLoadingView, causing warnings to be lost
when syncTimedOut && !workspaceSynced (where showKeypad is false) and the
recovery fallthrough is shown; update the rendering so pending.warnings is
visible in both branches — either hoist the warnings block out of the
isFinalizing conditional (render it above the
V2WorkspaceLoadingView/sync-timeout branching) or duplicate the same warnings
list into the sync-timeout/recovery branch that checks syncTimedOut and
workspaceSynced, using the same JSX structure and keys so warnings appear
whether finalizing succeeded or the sync-timeout recovery UI is shown.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx:
- Around line 526-536: mapHostProgressToInitStep can regress to "pending" when a
later step has finished but the prior hasn't flipped to "active"; fix by making
the mapping monotonic: in function mapHostProgressToInitStep (and using
HostProgressStep ids "registering", "creating_worktree", "ensuring_repo"), treat
"done" on later steps as evidence of progress — e.g. if
byId.get("creating_worktree") === "done" return "creating_worktree"; if
byId.get("ensuring_repo") === "done" return "syncing"; keep existing checks for
"active" and the registering/done => "ready" case so the UI never visually
rewinds to "pending" when a subsequent step is already done.
- Around line 517-521: The local HostProgressStep type duplicates the server
ProgressStep and can drift; replace it by deriving the client-side step type
from the tRPC procedure return type (use Awaited<ReturnType<typeof
client.workspaceCreation.getProgress.query>>['steps'][number] or equivalent) so
the client stays in sync with the server, and remove the hardcoded union
("pending"|"active"|"done"); update any mappers that inspect step.status (the
mapper that currently falls back to "pending") to handle the derived type
instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4850fa35-9da2-4fa8-abbd-4b4e71dfb00d

📥 Commits

Reviewing files that changed from the base of the PR and between 0769027 and 68e4cb9.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx

Comment on lines +415 to +424
{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>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Warnings are dropped when sync times out.

pending.warnings only renders inside V2WorkspaceLoadingView when isFinalizing is true. Once syncTimedOut && !workspaceSynced becomes true, showKeypad is false and the fallthrough recovery UI at lines 440-477 doesn't surface warnings — so any host warnings collected on success are silently lost in the very state where the user is stalling on a recoverable issue (likely the most useful place to see them).

Consider also rendering pending.warnings inside the sync-timeout block, or hoisting them above the conditional render so they appear in both success/recovery branches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
around lines 415 - 424, The current UI only renders pending.warnings when
isFinalizing is true inside V2WorkspaceLoadingView, causing warnings to be lost
when syncTimedOut && !workspaceSynced (where showKeypad is false) and the
recovery fallthrough is shown; update the rendering so pending.warnings is
visible in both branches — either hoist the warnings block out of the
isFinalizing conditional (render it above the
V2WorkspaceLoadingView/sync-timeout branching) or duplicate the same warnings
list into the sync-timeout/recovery branch that checks syncTimedOut and
workspaceSynced, using the same JSX structure and keys so warnings appear
whether finalizing succeeded or the sync-timeout recovery UI is shown.

The 700ms hold meant to let the keypad's last key animation finish was
buggy — doNavigate's useCallback dep on `pending` (a fresh object each
live-query tick) churned identity, so the effect cleared and re-armed
the timer every render and never actually navigated. Workspace creation
got stuck on the loading screen. Drop the hold; navigate immediately.
…n-from-v1-to-v2-with-separate

# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx (2)

18-18: 🛠️ Refactor suggestion | 🟠 Major

Co-locate V2WorkspaceLoadingView at a shared parent.

V2WorkspaceLoadingView (and its KeypadLoader / StepProgress deps) is now consumed by both this pending page and v2-workspace-loading/$workspaceId/page.tsx. Importing across a $workspaceId route segment couples component ownership to routing. Move it to e.g. _authenticated/_dashboard/components/V2WorkspaceLoadingView/ so both routes import from a stable location.

As per coding guidelines: "If a utility is used once, nest it under the parent's directory. If used 2+ times, promote it to the highest shared parent's directory or top-level components/."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
at line 18, V2WorkspaceLoadingView and its dependencies (KeypadLoader,
StepProgress) are being imported from the $workspaceId route which couples
component ownership to routing; move the V2WorkspaceLoadingView component and
its related components into a shared parent directory (e.g.,
_authenticated/_dashboard/components/V2WorkspaceLoadingView/) and update imports
in both the pending page (where V2WorkspaceLoadingView is currently imported)
and the v2-workspace-loading page so both import from the new stable shared
location, ensuring export names (V2WorkspaceLoadingView, KeypadLoader,
StepProgress) remain unchanged.

420-429: ⚠️ Potential issue | 🟡 Minor

Warnings are silently dropped on sync timeout.

pending.warnings only renders inside the isFinalizing branch of V2WorkspaceLoadingView. Once syncTimedOut && !workspaceSynced flips true, showKeypad becomes false and the recovery block at lines 445–482 has no warnings UI — so any host warnings collected at success are lost in the exact branch where they're most actionable. Either hoist the warnings list above the conditional render or duplicate the list into the sync-timeout branch.

Also applies to: 445-453

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
around lines 420 - 429, The warnings UI (pending.warnings) is only rendered
inside the isFinalizing branch of V2WorkspaceLoadingView so warnings are lost
when syncTimedOut && !workspaceSynced flips and showKeypad becomes false; fix by
hoisting the warnings list out of the isFinalizing conditional (render it
alongside the main loading view) or by duplicating the same warnings list into
the sync-timeout/recovery branch that is shown when syncTimedOut &&
!workspaceSynced, referencing the existing pending.warnings map and the same
list markup used in the current isFinalizing block to ensure parity.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx (1)

522-541: Optional: derive HostProgressStep from the tRPC query return type.

HostProgressStep is hand-rolled here while the real shape originates from client.workspaceCreation.getProgress.query (line 293). If the host-service ever adds a step status (e.g. "failed") or renames id values, this local type silently drifts and the mapper will fall through to "pending". Consider deriving it from the query result, e.g. NonNullable<Awaited<ReturnType<typeof client.workspaceCreation.getProgress.query>>>["steps"][number], or importing the shared host-service type if one is exported.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx
around lines 522 - 541, The locally-declared HostProgressStep type can drift
from the actual tRPC query shape used by
client.workspaceCreation.getProgress.query; update the code so HostProgressStep
is derived from the query return type instead of being hand-rolled (or import
the shared host-service type if available). Specifically, replace the manual
HostProgressStep declaration and use a type like
NonNullable<Awaited<ReturnType<typeof
client.workspaceCreation.getProgress.query>>>["steps"][number] (or the exported
host-service step type) and keep mapHostProgressToInitStep unchanged so it uses
the derived type for its steps parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx:
- Line 18: V2WorkspaceLoadingView and its dependencies (KeypadLoader,
StepProgress) are being imported from the $workspaceId route which couples
component ownership to routing; move the V2WorkspaceLoadingView component and
its related components into a shared parent directory (e.g.,
_authenticated/_dashboard/components/V2WorkspaceLoadingView/) and update imports
in both the pending page (where V2WorkspaceLoadingView is currently imported)
and the v2-workspace-loading page so both import from the new stable shared
location, ensuring export names (V2WorkspaceLoadingView, KeypadLoader,
StepProgress) remain unchanged.
- Around line 420-429: The warnings UI (pending.warnings) is only rendered
inside the isFinalizing branch of V2WorkspaceLoadingView so warnings are lost
when syncTimedOut && !workspaceSynced flips and showKeypad becomes false; fix by
hoisting the warnings list out of the isFinalizing conditional (render it
alongside the main loading view) or by duplicating the same warnings list into
the sync-timeout/recovery branch that is shown when syncTimedOut &&
!workspaceSynced, referencing the existing pending.warnings map and the same
list markup used in the current isFinalizing block to ensure parity.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/page.tsx:
- Around line 522-541: The locally-declared HostProgressStep type can drift from
the actual tRPC query shape used by client.workspaceCreation.getProgress.query;
update the code so HostProgressStep is derived from the query return type
instead of being hand-rolled (or import the shared host-service type if
available). Specifically, replace the manual HostProgressStep declaration and
use a type like NonNullable<Awaited<ReturnType<typeof
client.workspaceCreation.getProgress.query>>>["steps"][number] (or the exported
host-service step type) and keep mapHostProgressToInitStep unchanged so it uses
the derived type for its steps parameter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8298e9e2-c0ef-4799-8a6b-ebc5a7679b3e

📥 Commits

Reviewing files that changed from the base of the PR and between 68e4cb9 and f1d8a9d.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx

@AviPeltz AviPeltz merged commit 95cff6b into main Apr 27, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant