Skip to content

[codex] Reapply optimistic workspace Electric transaction work#4746

Merged
saddlepaddle merged 1 commit into
mainfrom
restore-tanstack-electric-transactions
May 20, 2026
Merged

[codex] Reapply optimistic workspace Electric transaction work#4746
saddlepaddle merged 1 commit into
mainfrom
restore-tanstack-electric-transactions

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented May 20, 2026

Summary

Reverts #4744, which reverted #4707. This restores the TanStack DB optimistic workspace creation and Electric txid-confirmed sync work from feat: optimistic workspace creation + Electric write-sync correctness.

Why

The revert PR was needed for the release handoff. With that release path handled elsewhere, this PR reapplies the transaction work on top of current main.

Impact

  • Restores optimistic v2Workspaces inserts and the failed-workspace-create local collection.
  • Restores Electric shape-stream retry handling and JWT refresh on 401.
  • Restores the txid/no-op write-path correctness changes across desktop, trpc, host-service, db utils, and electric-proxy.
  • Restores Electric-confirmed workspace deletion wait behavior.

Validation

  • bun run lint:fix
  • bun run lint

Open in Stage

Summary by cubic

Reapplies optimistic workspace creation with Electric txid-confirmed write sync. The app now shows “Creating…” until rows are synced, and waits for confirmed deletion before clearing UI state.

  • New Features

    • Optimistic v2Workspaces inserts with host create executed via mutation metadata; sync confirmation uses Electric txids.
    • Added local failedWorkspaceCreates collection to surface create errors on the workspace route.
    • Sidebar and routes use $synced as isSynced for “creating” state; deletion waits via waitForWorkspaceDeleted.
    • Submit API returns a handle { workspaceId, completed } to react to final outcome.
    • Electric shape-stream: added onError handler with JWT refresh on 401.
    • electric-proxy now uses ELECTRIC_PROTOCOL_QUERY_PARAMS.
  • Refactors

    • Removed renderer in-flight workspace store and cloud-row fallback; replaced with optimistic insert + sync waits.
    • Unified pane layout writes with writeWorkspacePaneLayout.
    • Replaced creationStatus UI with isSynced; updated sidebar, title, and shortcuts logic.
    • getCurrentTxid now returns a numeric txid; TRPC/host-service mutations wrap writes in transactions and return txids.
    • Dependencies: added @electric-sql/client to apps/electric-proxy.

Written for commit 120fb8d. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved workspace creation and deletion reliability through database-backed state tracking
    • Enhanced workspace synchronization status display for more accurate UI feedback
  • Improvements

    • Optimized workspace operations handling for more responsive interactions
    • Strengthened error handling and recovery for failed workspace operations

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eebf3954-3d41-4243-8a68-20f0535ccaaa

📥 Commits

Reviewing files that changed from the base of the PR and between 9d8a77c and 120fb8d.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (38)
  • apps/desktop/src/renderer/hooks/host-service/useWorkspaceHostUrl/useWorkspaceHostUrl.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarCollapsedWorkspaceButton/DashboardSidebarCollapsedWorkspaceButton.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/utils/getCreationStatusText.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/utils/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarShortcuts/useDashboardSidebarShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceTitle/V2WorkspaceTitle.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspaceV2/OpenInWorkspaceV2.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopoverV2/RunInWorkspacePopoverV2.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunIssuesInWorkspacePopover/RunIssuesInWorkspacePopover.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/WorkspaceCreateErrorState/WorkspaceCreateErrorState.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useBranchPickerController/useBranchPickerController.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/workspaceSyncWaits.ts
  • apps/desktop/src/renderer/routes/_authenticated/setup/adopt-worktrees/page.tsx
  • apps/desktop/src/renderer/stores/workspace-creates/Manager.tsx
  • apps/desktop/src/renderer/stores/workspace-creates/index.ts
  • apps/desktop/src/renderer/stores/workspace-creates/store.ts
  • apps/desktop/src/renderer/stores/workspace-creates/useWorkspaceCreates.ts
  • apps/desktop/src/renderer/stores/workspace-creates/writeWorkspacePaneLayout.ts
  • apps/electric-proxy/package.json
  • apps/electric-proxy/src/electric.ts
  • packages/db/src/utils/sql.ts
  • packages/host-service/src/trpc/router/workspaces/workspaces.ts
  • packages/trpc/src/router/chat/chat.ts
  • packages/trpc/src/router/v2-host/v2-host.ts
  • packages/trpc/src/router/v2-project/v2-project.test.ts
  • packages/trpc/src/router/v2-project/v2-project.ts
  • packages/trpc/src/router/v2-workspace/v2-workspace.ts

📝 Walkthrough

Walkthrough

This PR replaces in-memory workspace creation state with Electric-backed collections, substitutes creationStatus with database-driven isSynced flags throughout the dashboard UI, refactors the workspace creation hook to perform optimistic inserts with persisted failures, standardizes TRPC mutations to include transaction IDs, and implements centralized Electric sync error handling.

Changes

Workspace creation, UI state, and sync coordination

Layer / File(s) Summary
Dashboard sidebar UI state migration from creationStatus to isSynced
apps/desktop/.../DashboardSidebar/types.ts, apps/desktop/.../DashboardSidebarWorkspaceItem.tsx, apps/desktop/.../DashboardSidebarCollapsedWorkspaceButton.tsx, apps/desktop/.../DashboardSidebarExpandedWorkspaceRow.tsx, apps/desktop/.../DashboardSidebarWorkspaceIcon.tsx
Replace creationStatus?: "preparing" | "generating-branch" | "creating" | "failed" with isSynced: boolean field in workspace model; propagate through sidebar component hierarchy (item → collapsed button/expanded row → icon) and derive UI pending state from !isSynced.
Dashboard sidebar data hooks and shortcuts filtering
apps/desktop/.../useDashboardSidebarData.ts, apps/desktop/.../useDashboardSidebarShortcuts.ts, apps/desktop/.../TopBar/V2WorkspaceTitle.tsx, apps/desktop/.../layout.tsx
Remove in-flight workspace creation rows and useWorkspaceCreatesStore imports; add isSynced from workspaces.$synced to live query results; filter workspace shortcuts by isSynced and deletion status; remove WorkspaceCreatesManager component.
Workspace creation hook and store refactoring
apps/desktop/.../workspace-creates/index.ts, apps/desktop/.../workspace-creates/useWorkspaceCreates.ts, apps/desktop/.../workspace-creates/writeWorkspacePaneLayout.ts
Remove useWorkspaceCreatesStore, InFlightEntry, and WorkspaceCreatesManager; refactor useWorkspaceCreates to export only submit(args) => SubmitHandle with completed promise; implement optimistic workspace insert and pane layout writing via new writeWorkspacePaneLayout helper.
Failed workspace creation schema and collection setup
apps/desktop/.../CollectionsProvider/dashboardSidebarLocal/schema.ts, apps/desktop/.../CollectionsProvider/collections.ts
Add failedWorkspaceCreateSchema and FailedWorkspaceCreateRow type for persisting creation failures; add failedWorkspaceCreates local-storage collection to OrgCollections.
Workspace creation error state component refactoring
apps/desktop/.../v2-workspace/components/WorkspaceCreateErrorState/WorkspaceCreateErrorState.tsx
Refactor from separate workspaceId, name, branch, error props to single entry: FailedWorkspaceCreateRow prop; rewrite retry/dismiss to interact with useWorkspaceCreates().submit and collections.failedWorkspaceCreates.
End-to-end workspace creation flow with optimistic navigation
apps/desktop/.../useBranchPickerController.ts, apps/desktop/.../useSubmitWorkspace.ts, apps/desktop/.../adopt-worktrees/page.tsx
Update entry points to destructure { workspaceId, completed } from submit(...) instead of awaiting single result; perform optimistic navigation and reconcile via completed.then(...) when final workspaceId differs.
Task/issue run popover flow updates for new submit API
apps/desktop/.../RunInWorkspacePopoverV2.tsx, apps/desktop/.../RunIssuesInWorkspacePopover.tsx, apps/desktop/.../OpenInWorkspaceV2.tsx
Map submit(...) calls to handle collections; aggregate outcomes from handle.completed promises via Promise.all; construct error messages from failed outcomes instead of awaiting submit results directly.
Workspace layout rendering with isSynced gates and failed-entry lookup
apps/desktop/.../v2-workspace/layout.tsx
Add live query for failedWorkspaceCreates; compute isSynced from workspace $synced flag; gate sidebar/host-status initialization on synced state; render WorkspaceCreateErrorState (with failed entry), WorkspaceCreatingState (unsynced with workspace row data), or main content based on workspace state.
Workspace deletion sync coordination and shortcut filtering
apps/desktop/.../CollectionsProvider/workspaceSyncWaits.ts, apps/desktop/.../useDestroyDialogState.ts, apps/desktop/.../useDashboardSidebarShortcuts.ts
Add waitForWorkspaceDeleted helper to await deletion propagation in Electric collection with timeout; update delete dialog to coordinate deletion with sync wait; filter shortcuts to exclude deleting workspaces.
Host URL hook gating on workspace sync status
apps/desktop/.../useWorkspaceHostUrl.ts
Include isSynced from workspaces.$synced in workspace lookup; return status: loading when workspace exists but !isSynced, delaying ready/local-starting outcomes until sync completes.

TRPC mutation transaction ID standardization

Layer / File(s) Summary
Database transaction ID retrieval and validation
packages/db/src/utils/sql.ts
Update getCurrentTxid to cast SQL output via ::xid::text, parse defensively with Number.isSafeInteger validation, and throw with raw value on parse failure.
Workspace mutations with transaction ID propagation
packages/trpc/src/router/v2-workspace/v2-workspace.ts
Add txid to responses for create, setTask, updateNameFromHost, delete, deleteMainForHost by wrapping in transactions and including getCurrentTxid(); throw NOT_FOUND when updates don't match.
Project mutations with transaction ID propagation
packages/trpc/src/router/v2-project/v2-project.ts, packages/trpc/src/router/v2-project/v2-project.test.ts
Wrap v2ProjectRouter.create insert in transaction; compute and return txid alongside project; update test mocks to return numeric txid: 123 and assert txid in all mutation results.
Host membership mutations with transaction ID and not-found validation
packages/trpc/src/router/v2-host/v2-host.ts
Update rename, removeMember, setMemberRole to use returning for row-match detection; throw NOT_FOUND when operations don't affect rows; propagate txid from transactional operations.
Chat mutations with transaction ID propagation
packages/trpc/src/router/chat/chat.ts
Wrap createSession and deleteSession in transactions; compute txid via getCurrentTxid(); return txid: null for no-op paths (conflict on create, no row deleted).
Host service workspace creation txid extraction
packages/host-service/src/trpc/router/workspaces/workspaces.ts
Add extractCreateTxid helper to read optional txid from cloud workspace payload; include txid: number | null in workspacesRouter.create response.

Electric client infrastructure and error handling

Layer / File(s) Summary
Electric sync error handling and JWT refresh
apps/desktop/.../CollectionsProvider/collections.ts
Add handleElectricSyncError handler that catches 401 FetchErrors, refreshes JWT via authClient.token() and setJwt, otherwise logs sync errors; wire into all organizational collections' shapeOptions.onError.
Electric transaction ID validation and mutation hook updates
apps/desktop/.../CollectionsProvider/collections.ts
Add electricTxidMatch helper to normalize txid values; update all collection mutation hooks to return validated txid via helper; add special v2Workspaces.onInsert logic to call host-service workspaces.create and store result in metadata.
Electric protocol parameters externalization
apps/electric-proxy/src/electric.ts, apps/electric-proxy/package.json
Replace hardcoded PROTOCOL_PARAMS array with import from @electric-sql/client@1.5.15; initialize PROTOCOL_PARAMS via new Set(ELECTRIC_PROTOCOL_QUERY_PARAMS).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • superset-sh/superset#4120: Earlier PR introducing optimistic workspace creation and $synced gating that this PR builds upon by replacing creationStatus state model with isSynced-only rendering.
  • superset-sh/superset#4707: Overlapping work on optimistic creation flow, Electric sync correctness, sidebar/workspace deletion hooks, and isSynced/$synced state gating.
  • superset-sh/superset#4406: Concurrent modifications to useWorkspaceHostUrl host-target logic and relay-URL sourcing that interact with this PR's isSynced gating.

🐰 Hop hop, the workspace syncs so true,
From in-flight dreams to Electric's stable crew,
No more in-memory whispers wandering free,
Just clean collections and isSynced to see! ✨

✨ 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 restore-tanstack-electric-transactions
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch restore-tanstack-electric-transactions

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 20, 2026

@saddlepaddle saddlepaddle merged commit fa74244 into main May 20, 2026
14 of 16 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 20, 2026

Greptile Summary

This PR reapplies the optimistic workspace creation + Electric txid-confirmed write-sync work that was temporarily reverted for a release handoff. It replaces the previous renderer-side in-flight store with TanStack DB optimistic inserts on v2Workspaces (using $synced as the creating indicator), adds a failedWorkspaceCreates local collection for error state, and wraps all mutating TRPC endpoints in transactions that return Electric txids so the client can wait for confirmed sync.

  • All Electric shape streams gain an onError handler that refreshes the JWT on 401; all mutating endpoints (v2-workspace, v2-host, v2-project, chat) now execute inside transactions and return numeric txids aligned with Electric's 32-bit xid representation (pg_current_xact_id()::xid::text).
  • Workspace deletion now waits up to 30 s for Electric to confirm removal before clearing UI state; waitForWorkspaceDeleted in workspaceSyncWaits.ts handles the subscription + timeout pattern.
  • useSubmitWorkspace and callers migrated from Promise<SubmitResult> to a synchronous SubmitHandle { workspaceId, completed } so navigation can happen immediately while the async outcome is handled separately.

Confidence Score: 3/5

Safe to merge after fixing the dropped warnings regression in useWorkspaceCreates.ts.

The txid plumbing, Electric error handler, and optimistic insert flow are all coherent and tests were updated accordingly. The one clear regression is that result.warnings from workspaces.create — which the old dispatch loop surfaced as toast notifications — are never accessed in the new isPersisted.promise.then handler, silently losing any warning messages the host service returns. The remaining findings (z.custom without a predicate, JWT no-op on empty token) are lower-risk quality issues.

useWorkspaceCreates.ts (dropped warnings), dashboardSidebarLocal/schema.ts (unvalidated input field), collections.ts (JWT refresh silent no-op path)

Important Files Changed

Filename Overview
apps/desktop/src/renderer/stores/workspace-creates/useWorkspaceCreates.ts Core refactor from async-imperative to optimistic-insert + isPersisted promise; host-service warnings are silently dropped in the new completed handler.
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts Adds onError JWT-refresh handler to all Electric shape streams and v2Workspaces onInsert that drives workspace creation; JWT refresh has a silent no-op path when the token endpoint returns an empty token.
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts Adds failedWorkspaceCreateSchema; the input field uses z.custom without a runtime predicate, so malformed localStorage data passes validation silently.
packages/trpc/src/router/v2-workspace/v2-workspace.ts All mutating endpoints now wrap writes in transactions and return the Electric txid; correctly handles already-gone rows by returning txid: null.
packages/db/src/utils/sql.ts getCurrentTxid now casts xid8→xid before converting to text, aligning the returned value with Electric's 32-bit xid representation.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx Replaces in-flight store with $synced flag and failedWorkspaceCreates collection for creating/error states; logic and state transitions are correct.

Comments Outside Diff (3)

  1. apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts, line 519 (link)

    P2 z.custom<WorkspacesCreateInput>() without a predicate argument accepts any value at runtime — it only provides a TypeScript type assertion, not actual validation. If the stored localStorage entry for input has a stale or incompatible shape (e.g. after a schema change between app versions), the malformed data passes validation silently and will only fail later when entry.input is used in the retry path, with an opaque error.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
    Line: 519
    
    Comment:
    `z.custom<WorkspacesCreateInput>()` without a predicate argument accepts any value at runtime — it only provides a TypeScript type assertion, not actual validation. If the stored localStorage entry for `input` has a stale or incompatible shape (e.g. after a schema change between app versions), the malformed data passes validation silently and will only fail later when `entry.input` is used in the retry path, with an opaque error.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts, line 165-179 (link)

    P2 If authClient.token() succeeds but returns a response where result.data?.token is falsy (e.g. the refresh endpoint returned an empty or null token), setJwt is not called and Electric retries with the same expired JWT, producing another 401 → another onError call → another silent no-op. This creates an infinite retry cycle on a fully-expired session instead of surfacing the auth failure to the user.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
    Line: 165-179
    
    Comment:
    If `authClient.token()` succeeds but returns a response where `result.data?.token` is falsy (e.g. the refresh endpoint returned an empty or null token), `setJwt` is not called and Electric retries with the same expired JWT, producing another 401 → another `onError` call → another silent no-op. This creates an infinite retry cycle on a fully-expired session instead of surfacing the auth failure to the user.
    
    How can I resolve this? If you propose a fix, please make it concise.
  3. apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts, line 140-165 (link)

    P2 When waitForWorkspaceDeleted times out, keepDeleting = true prevents the finally block from calling clearDeleting(workspaceId). There is no subsequent mechanism that calls clearDeleting — the workspace remains permanently marked as "deleting" in DeletingWorkspacesProvider for the rest of the session. If Electric eventually delivers the deletion the sidebar item disappears and the badge becomes moot, but if sync is permanently delayed (e.g. the user stays offline), the entry leaks in the provider until restart.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts
    Line: 140-165
    
    Comment:
    When `waitForWorkspaceDeleted` times out, `keepDeleting = true` prevents the `finally` block from calling `clearDeleting(workspaceId)`. There is no subsequent mechanism that calls `clearDeleting` — the workspace remains permanently marked as "deleting" in `DeletingWorkspacesProvider` for the rest of the session. If Electric eventually delivers the deletion the sidebar item disappears and the badge becomes moot, but if sync is permanently delayed (e.g. the user stays offline), the entry leaks in the provider until restart.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
apps/desktop/src/renderer/stores/workspace-creates/useWorkspaceCreates.ts:125-141
Warnings from the host service are silently dropped. The previous implementation iterated `result.warnings ?? []` and called `toast.warning` for each entry after a successful create. The new `isPersisted.promise.then` handler reads `metadata.result` but never touches `result.warnings`, so any host-service warnings (e.g. about agents, branch state, or quota limits) are lost without any user notification. Add `for (const w of result.warnings ?? []) toast.warning(w)` before the `return` at line 140.

### Issue 2 of 4
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts:519
`z.custom<WorkspacesCreateInput>()` without a predicate argument accepts any value at runtime — it only provides a TypeScript type assertion, not actual validation. If the stored localStorage entry for `input` has a stale or incompatible shape (e.g. after a schema change between app versions), the malformed data passes validation silently and will only fail later when `entry.input` is used in the retry path, with an opaque error.

### Issue 3 of 4
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts:165-179
If `authClient.token()` succeeds but returns a response where `result.data?.token` is falsy (e.g. the refresh endpoint returned an empty or null token), `setJwt` is not called and Electric retries with the same expired JWT, producing another 401 → another `onError` call → another silent no-op. This creates an infinite retry cycle on a fully-expired session instead of surfacing the auth failure to the user.

### Issue 4 of 4
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts:140-165
When `waitForWorkspaceDeleted` times out, `keepDeleting = true` prevents the `finally` block from calling `clearDeleting(workspaceId)`. There is no subsequent mechanism that calls `clearDeleting` — the workspace remains permanently marked as "deleting" in `DeletingWorkspacesProvider` for the rest of the session. If Electric eventually delivers the deletion the sidebar item disappears and the badge becomes moot, but if sync is permanently delayed (e.g. the user stays offline), the entry leaks in the provider until restart.

Reviews (1): Last reviewed commit: "Reapply "feat: optimistic workspace crea..." | Re-trigger Greptile

Comment on lines +125 to +141
const completed = transaction.isPersisted.promise
.then<SubmitOutcome>(() => {
const result = metadata.result;
if (!result) {
return { ok: true, workspaceId };
}
writeWorkspacePaneLayout(
collections,
result.workspace,
result.terminals,
result.agents,
);
if (result.workspace.id !== workspaceId) {
deleteWorkspaceLocalState(workspaceId);
}
return { ok: true, workspaceId: result.workspace.id };
})
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.

P1 Warnings from the host service are silently dropped. The previous implementation iterated result.warnings ?? [] and called toast.warning for each entry after a successful create. The new isPersisted.promise.then handler reads metadata.result but never touches result.warnings, so any host-service warnings (e.g. about agents, branch state, or quota limits) are lost without any user notification. Add for (const w of result.warnings ?? []) toast.warning(w) before the return at line 140.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/stores/workspace-creates/useWorkspaceCreates.ts
Line: 125-141

Comment:
Warnings from the host service are silently dropped. The previous implementation iterated `result.warnings ?? []` and called `toast.warning` for each entry after a successful create. The new `isPersisted.promise.then` handler reads `metadata.result` but never touches `result.warnings`, so any host-service warnings (e.g. about agents, branch state, or quota limits) are lost without any user notification. Add `for (const w of result.warnings ?? []) toast.warning(w)` before the `return` at line 140.

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

sazabi Bot pushed a commit that referenced this pull request May 20, 2026
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