Skip to content

feat(desktop): inline keypad loader on v2 workspace hydration#3821

Closed
saddlepaddle wants to merge 2 commits into
mainfrom
inline-v2-workspace-loading-keypad
Closed

feat(desktop): inline keypad loader on v2 workspace hydration#3821
saddlepaddle wants to merge 2 commits into
mainfrom
inline-v2-workspace-loading-keypad

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 28, 2026

Follow-up to #3820 (the revert).

Restores the keypad animation #3788 was after, but as a 5-line conditional render in v2-workspace/layout.tsx instead of a separate route + duplicate component tree.

  • Same useLiveQuery/isReady everywhere — no asymmetric query, no cross-route navigation, no oscillation.
  • Reuses the existing v1 KeypadLoader + StepProgress (purely presentational — no v1 store/trpc coupling).
  • Layout select now includes name so the loader can show the workspace name.

Test plan

  • Cold-launch on a fresh install → keypad shows briefly, then resolves to the workspace
  • Open existing workspace → no flicker
  • Slow v2Hosts hydration (throttle network) → keypad stays up; no redirect-loop, no console errors
  • Open non-existent workspace → falls through to WorkspaceNotFoundState once isReady

Summary by cubic

Inlines the keypad loading screen in v2-workspace/layout.tsx so v2 workspaces show the loader during hydration without navigating to a separate route. This removes the redirect loop caused by asymmetric queries and reuses the v1 KeypadLoader and StepProgress.

  • Bug Fixes

    • Eliminates navigation oscillation by using a single useLiveQuery/isReady path in the layout (no cross-route redirects).
    • Loader stays visible while v2Hosts hydrate on slow networks; no flicker or redirect loops.
  • Refactors

    • Removes the /v2-workspace-loading route and all duplicated loader assets/components.
    • Adds the workspace name to the layout query to show it in the inline loader.
    • Reuses v1 KeypadLoader and StepProgress from renderer/screens/main/components/WorkspaceView/WorkspaceInitializingView/*.
    • Simplifies the pending workspace page to show a small inline progress list and actions (no keypad UI).

Written for commit 331fc12. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Enhanced workspace creation progress display with step-based status indicators showing initialization steps
    • Improved workspace ready state with dedicated "opening workspace" status
  • Refactor

    • Integrated workspace loading progress directly into the workspace view for a streamlined experience
    • Updated progress visualization with clearer step indicators and better user feedback

Replaces #3788's separate /v2-workspace-loading route with an inline render
in v2-workspace/layout.tsx. The route + duplicate component tree were the
source of the navigation oscillation crash: layout queried v2Workspaces +
v2Hosts (joined isReady), the loading route queried only v2Workspaces, so
the two effects ping-ponged when v2Hosts hydrated slower.

Reuses the existing v1 KeypadLoader + StepProgress (presentational only;
no v1 store coupling) instead of duplicating assets.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ed1b1bc-cf75-42b7-9f91-fc0c0b23321e

📥 Commits

Reviewing files that changed from the base of the PR and between 89285e4 and 331fc12.

⛔ 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/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/V2WorkspaceLoadingView/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx

📝 Walkthrough

Walkthrough

This PR removes the keypad-based workspace loading UI (KeypadLoader and StepProgress components) and the intermediate v2-workspace-loading route. The pending page now renders progress steps inline from the host progress object. The v2-workspace layout conditionally renders a simplified loading view instead of navigating away.

Changes

Cohort / File(s) Summary
Pending Page Refactor
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx
Replaces keypad-based loading UI with inline "creating workspace" view directly rendering host progress.steps as a status list. Removes keypad-to-init-step conversion logic and adds dedicated success/stall/failed state sections.
Removed KeypadLoader Components
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/KeypadLoader/*
Deletes the entire KeypadLoader component (KeypadLoader.tsx), its CSS styling (KeypadLoader.css), and removes its re-export from index.ts.
Removed StepProgress Components
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/components/V2WorkspaceLoadingView/StepProgress/*
Deletes the StepProgress component (StepProgress.tsx), its animation styling (StepProgress.css), and removes its re-export from index.ts.
Removed v2-workspace-loading Route
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx
Deletes the intermediate loading route handler that fetched workspace records and navigated to the workspace detail page.
Simplified V2WorkspaceLoadingView
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx
Removes title, description, children, and currentStep props. Hard-codes heading and derives currentStep from timer. Updates component imports to use shared workspace-initialization modules.
Updated v2-workspace Layout
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx
Removes route navigation based on readiness. Conditionally renders loading view inline when not ready instead of navigating away. Expands live-query selection to include workspace name.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 The keypad hops away, the steps now march inline,
No loading route to dance through—just progress, clear and fine,
Simplified the journey, made the flow more neat,
Workspace loads with grace now, the refactoring's complete! ✨

✨ 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 inline-v2-workspace-loading-keypad

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 28, 2026

Greptile Summary

This PR removes the dedicated v2-workspace-loading route (plus its duplicated KeypadLoader, StepProgress components and assets — ~750 lines deleted) and replaces it with a 5-line !isReady guard in v2-workspace/layout.tsx that renders an inline V2WorkspaceLoadingView. The new view reuses the v1 KeypadLoader and StepProgress (purely presentational), avoiding the asymmetric query and cross-route navigation that caused oscillation in the reverted PR. The pending page is cleaned up to remove a now-redundant import.

Confidence Score: 5/5

Safe to merge — a straightforward simplification with no correctness issues.

All findings are P2 (style/efficiency): the setInterval running past max step is harmless (React bails on same-value state updates), and the workspaceName being unreachable during loading is a no-op since the component handles undefined gracefully. No data-integrity, routing, or runtime defects introduced.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx Adds inline !isReady guard that renders V2WorkspaceLoadingView before the live query resolves; adds name to the select projection (unused during loading). Core logic is clean.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx New lightweight loader component that reuses v1 KeypadLoader + StepProgress with a fake timer-driven step progression (400 ms/step). Timer runs past max step unnecessarily but causes no visible defect.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx Removes stale V2WorkspaceLoadingView import and adds HiCheck for the "Workspace ready — opening..." success state. No logical changes to the creation/retry flow.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace-loading/$workspaceId/page.tsx Entire route deleted (38 lines); replaced by the inline conditional in the v2-workspace layout. No remaining references to this route in the codebase.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User navigates to /v2-workspace/$workspaceId] --> B{workspaceId resolved?}
    B -- No --> C[return null]
    B -- Yes --> D{useLiveQuery isReady?}
    D -- No --> E[V2WorkspaceLoadingView\nfake timer-driven KeypadLoader\nworkspaceName always undefined here]
    D -- Yes --> F{workspace and hostUrl found?}
    F -- No --> G[WorkspaceNotFoundState]
    F -- Yes --> H[WorkspaceTrpcProvider + Outlet]
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/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx
Line: 24-28

Comment:
**Interval keeps firing after reaching max step**

Once `stepIdx` reaches `VISIBLE_STEPS.length - 1`, `Math.min` returns the same value on every tick, so the callback fires every 400 ms indefinitely but never produces a state change (React bails out). This is harmless in practice, but clearing the interval early would be cleaner.

```suggestion
	useEffect(() => {
		const id = window.setInterval(() => {
			setStepIdx((prev) => {
				const next = Math.min(prev + 1, VISIBLE_STEPS.length - 1);
				if (next === VISIBLE_STEPS.length - 1) window.clearInterval(id);
				return next;
			});
		}, STEP_INTERVAL_MS);
		return () => window.clearInterval(id);
	}, []);
```

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: 76-78

Comment:
**`workspaceName` will always be `undefined` during loading**

When `!isReady`, `useLiveQuery` hasn't resolved its first result, so `workspacesWithHost` is the default `[]`, making `workspace` null and `workspace?.name` always `undefined` at the point this branch is reached. The `name` field added to the `select` projection is effectively unreachable from this render path. The loader handles `undefined` gracefully, so there's no bug — but the workspace subtitle will never appear here unless `useLiveQuery` begins returning cached data before `isReady` becomes true.

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

Reviews (1): Last reviewed commit: "feat(desktop): inline keypad loader on v..." | Re-trigger Greptile

Comment on lines 24 to 28
useEffect(() => {
if (currentStepProp !== undefined) return;
const id = window.setInterval(() => {
setStepIdx((prev) => Math.min(prev + 1, VISIBLE_STEPS.length - 1));
}, STEP_INTERVAL_MS);
return () => window.clearInterval(id);
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 Interval keeps firing after reaching max step

Once stepIdx reaches VISIBLE_STEPS.length - 1, Math.min returns the same value on every tick, so the callback fires every 400 ms indefinitely but never produces a state change (React bails out). This is harmless in practice, but clearing the interval early would be cleaner.

Suggested change
useEffect(() => {
if (currentStepProp !== undefined) return;
const id = window.setInterval(() => {
setStepIdx((prev) => Math.min(prev + 1, VISIBLE_STEPS.length - 1));
}, STEP_INTERVAL_MS);
return () => window.clearInterval(id);
useEffect(() => {
const id = window.setInterval(() => {
setStepIdx((prev) => {
const next = Math.min(prev + 1, VISIBLE_STEPS.length - 1);
if (next === VISIBLE_STEPS.length - 1) window.clearInterval(id);
return next;
});
}, STEP_INTERVAL_MS);
return () => window.clearInterval(id);
}, []);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/V2WorkspaceLoadingView/V2WorkspaceLoadingView.tsx
Line: 24-28

Comment:
**Interval keeps firing after reaching max step**

Once `stepIdx` reaches `VISIBLE_STEPS.length - 1`, `Math.min` returns the same value on every tick, so the callback fires every 400 ms indefinitely but never produces a state change (React bails out). This is harmless in practice, but clearing the interval early would be cleaner.

```suggestion
	useEffect(() => {
		const id = window.setInterval(() => {
			setStepIdx((prev) => {
				const next = Math.min(prev + 1, VISIBLE_STEPS.length - 1);
				if (next === VISIBLE_STEPS.length - 1) window.clearInterval(id);
				return next;
			});
		}, STEP_INTERVAL_MS);
		return () => window.clearInterval(id);
	}, []);
```

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

Comment on lines +76 to +78
if (!isReady) {
return <V2WorkspaceLoadingView workspaceName={workspace?.name} />;
}
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 workspaceName will always be undefined during loading

When !isReady, useLiveQuery hasn't resolved its first result, so workspacesWithHost is the default [], making workspace null and workspace?.name always undefined at the point this branch is reached. The name field added to the select projection is effectively unreachable from this render path. The loader handles undefined gracefully, so there's no bug — but the workspace subtitle will never appear here unless useLiveQuery begins returning cached data before isReady becomes true.

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: 76-78

Comment:
**`workspaceName` will always be `undefined` during loading**

When `!isReady`, `useLiveQuery` hasn't resolved its first result, so `workspacesWithHost` is the default `[]`, making `workspace` null and `workspace?.name` always `undefined` at the point this branch is reached. The `name` field added to the `select` projection is effectively unreachable from this render path. The loader handles `undefined` gracefully, so there's no bug — but the workspace subtitle will never appear here unless `useLiveQuery` begins returning cached data before `isReady` becomes true.

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

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