Skip to content

feat(sdk): org delete blockers UI - #2025

Merged
rohanchkrabrty merged 20 commits into
mainfrom
feat/workspace-delete-blockers-dialog
Nov 5, 2026
Merged

rohanchkrabrty merged 20 commits into
mainfrom
feat/workspace-delete-blockers-dialog

Conversation

@rohanchkrabrty

@rohanchkrabrty rohanchkrabrty commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • The delete organization button runs CheckOrganizationDelete on click, with a spinner on the button. When something blocks the delete, an "Unable to delete" dialog lists the blockers. Otherwise the confirmation opens.
  • A refused delete (FAILED_PRECONDITION with PreconditionFailure violations) closes the confirmation and opens the same blocked dialog. A FAILED_PRECONDITION with no violations comes from a disabled organization and shows as its own blocker.
  • FrontierProvider takes a slots prop. Its deleteBlockedDialog slot lets a host app word the blocked dialog in one place. The slot returns DeleteBlockedDialog (title, description, blockers, messages), its own AlertDialog.Content to replace the whole dialog, or undefined for the SDK's own dialog. A bullet is messages[subject], else messages[type], else the SDK line. Identical lines show once, and blockers with no line share one generic line.
  • A refused delete whose blockers can't be read shows a toast instead of the dialog.
  • DeleteBlocker (type, subject, count) is the SDK's own shape. A check response and a refused delete both map to it. The server's developer message is left out.
  • useDeleteCheck runs the check, then opens the confirmation or the blocked dialog. The project flow in the next PR uses it too.
  • Bumps @raystack/proton to 94668c36, the commit the server builds from.
<FrontierProvider
  config={config}
  slots={{
    deleteBlockedDialog: ctx =>
      ctx.entity === 'organization' ? (
        <DeleteBlockedDialog messages={{ 'app/report': 'Reports exist: Delete them first.' }} />
      ) : undefined
  }}
>

Stack

  1. This PR: shared pieces and the organization delete
  2. feat(sdk): project delete blockers UI #2041: project delete
  3. feat(sdk): user delete blockers UI #2042: account delete

Test plan

  • tsc --noEmit shows no new errors, eslint, pnpm build
  • Delete a workspace with an active paid subscription: the blocked dialog lists it
  • Delete a clean workspace: the confirmation opens and the delete goes through
  • Delete a disabled workspace: the blocked dialog says it is disabled

The delete organization button no longer checks the blockers on page load
and stays disabled with a tooltip. On click it runs checkOrganizationDelete
with a loading state. If the delete is blocked, a dialog lists the
blockers; otherwise the delete dialog opens.
Brings in CheckProjectDelete, CheckCurrentUserDelete, DeleteCurrentUser,
the top-level Blocker message with count, and the org resource RPCs.
A disabled organization fails the check with FAILED_PRECONDITION, so it
shows as a blocker in the same dialog. Any other check error shows a
toast. The delete button keeps its label while the check runs.
@vercel

vercel Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
frontier Ready Ready Preview Nov 5, 2026 7:01am UTC

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review
Summary

Summary by CodeRabbit

  • New Features
    • Added an organization deletion flow that checks eligibility before showing the confirmation dialog and displays a checking state.
    • Added a blocked-deletion dialog that explains why deletion cannot proceed, with customizable content and messages.
    • Added SDK options for customizing the blocked-deletion experience.
  • Bug Fixes
    • Improved deletion error feedback for blocked deletions, permission issues, and organizations that cannot be found.

The SDK now parses deletion blockers into typed data, formats blocker messages, and supports a customizable blocked-delete dialog. The organization view checks deletion eligibility when deletion starts and routes the result to the confirmation or blocked dialog.

Changes: Organization deletion flow

Layer / File(s) Summary
Blocker data and messages
web/sdk/shared/types.ts, web/sdk/client/utils/delete-blockers.ts, web/sdk/client/utils/delete-blockers.test.ts
The SDK adds a typed blocker shape and maps precondition violations to blockers. Message formatting supports subject-specific and type-specific text, removes duplicate lines, and adds a generic line when needed. The previous tests for blocker wording and error parsing are removed.
Blocked-delete dialog and provider slot
web/sdk/shared/types.ts, web/sdk/client/contexts/FrontierContext.tsx, web/sdk/client/components/delete-blocked-dialog/*, web/sdk/client/index.ts
The provider accepts optional slots, including a blocked-delete dialog slot. The dialog host passes blocker context to the slot and renders the default dialog when the slot result is null or boolean. The SDK exports the dialog and related types.
Deletion check and confirmation flow
web/sdk/client/hooks/useDeleteFlow.ts, web/sdk/client/views/general/general-view.tsx, web/sdk/client/views/general/components/delete-organization-dialog.tsx, web/sdk/client/views/general/general-view.module.css, web/sdk/package.json
The organization view starts an eligibility check from the Delete button and shows a loading state while checking. The flow opens the confirmation or blocked dialog and routes errors. The confirmation dialog uses an alert-dialog handle and forwards deletion errors. The pinned Proton revision changes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Possibly related PRs

  • raystack/frontier#2017: Adds organization resource blockers to the check response and delete precondition path consumed by this SDK flow.
  • raystack/frontier#2005: Adds the blocker type and count field that the SDK blocker model carries.
  • raystack/frontier#2011: Passes blocker counts through the check response that the SDK copies into DeleteBlocker.
Merge Risk: Low · previous review 5cacc

The change is mergeable with awareness of remaining dialog issues: blocker explanations can be missing or stale, and switching organizations during a check can open an unchecked confirmation. Server-side deletion safeguards remain intact.

Pre-merge checks: Passed 2

Check name Status Explanation
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.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

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

Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 56698ff and 5cacc30.

Files selected for processing: 9

  • web/sdk/client/components/delete-blocked-dialog/delete-blocked-dialog.tsx
  • web/sdk/client/components/delete-blocked-dialog/index.ts
  • web/sdk/client/hooks/useDeleteFlow.ts
  • web/sdk/client/utils/delete-blockers.test.ts
  • web/sdk/client/utils/delete-blockers.ts
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx
  • web/sdk/client/views/general/general-view.module.css
  • web/sdk/client/views/general/general-view.tsx
  • web/sdk/shared/types.ts
Files with no reviewable changes: 1

  • web/sdk/client/utils/delete-blockers.test.ts
Files skipped from review as they are similar to previous changes: 1

  • web/sdk/client/views/general/components/delete-organization-dialog.tsx

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

The form now mounts only while the dialog is open, so it starts empty on
every open and fetches the token balance then, without an open prop.
@coveralls

coveralls commented Oct 26, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 41984296836

Coverage remained the same at 56.053%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 42189
Covered Lines: 23648
Line Coverage: 56.05%
Coverage Strength: 19.7 hits per line

💛 - Coveralls

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Close the shared handle before invoking the success… · delete-organization-dialog.tsx:84-94

web/sdk/client/views/general/components/delete-organization-dialog.tsx:84-94
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Close the shared handle before invoking the success callback.

DeleteOrganizationForm invokes onDeleteSuccess without closing the AlertDialog. The demo callback navigates away, but the module-scoped handle retains its open state. Returning to GeneralView can reopen the confirmation dialog.

Close the handle in DeleteOrganizationDialog, where handle is in scope:

Suggested fix
-      <DeleteOrganizationForm onDeleteSuccess={onDeleteSuccess} />
+      <DeleteOrganizationForm
+        onDeleteSuccess={() => {
+          handle.close();
+          onDeleteSuccess?.();
+        }}
+      />

Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 9655c98 and 488d0ca.

Files ignored due to path filters: 1
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 3
  • web/sdk/client/utils/delete-blockers.ts
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx
  • web/sdk/client/views/general/general-view.tsx

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.

FrontierProvider takes a slots prop. Its deleteBlockedDialog slot gets the
blockers of a delete and returns a DeleteBlockedDialog with the host app's
title, description and one line per resource namespace or blocker type.
With no slot, or when the slot returns undefined, the SDK shows its own
dialog.

DeleteBlocker is the SDK's own shape for a blocker. A delete check and a
refused delete both give it, without the server's developer message.

The organization delete uses a shared useDeleteFlow hook: check on click,
then open the confirmation or the blocked dialog. A refused delete now
closes the confirmation and shows the blocked dialog instead of a toast.
A failed_precondition with no violations means a disabled organization.

Bumps @raystack/proton to 94668c36, the commit the server builds from.
@rohanchkrabrty rohanchkrabrty mentioned this pull request Nov 3, 2026
1 of 3 tasks
@rohanchkrabrty rohanchkrabrty changed the title feat(sdk): check org delete blockers on click and show a dialog feat(sdk): check org delete blockers on click and let the host app word the dialog Nov 3, 2026
@rohanchkrabrty rohanchkrabrty changed the title feat(sdk): check org delete blockers on click and let the host app word the dialog feat(sdk): org delete blockers UI Nov 3, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Follow-up Comments

Outside diff Invalidate obsolete delete checks when the hook owner unmounts.

44-56: Minor · Stability & Availability · Quick win

Invalidate obsolete delete checks when the hook owner unmounts.

startDelete can publish after GeneralView unmounts. If the user returns to general before check(payload) resolves, the old continuation can open the new instance's confirmation or blocked dialog through the module-scoped handle. Add a request-generation guard and invalidate it during cleanup. Guard the error and finally paths so an obsolete request cannot publish or clear isChecking for a replacement request.

Suggested fix
-import { useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
...
   const t = useTerminology();
   const [isChecking, setIsChecking] = useState(false);
+  const requestGeneration = useRef(0);
+
+  useEffect(() => {
+    return () => {
+      requestGeneration.current += 1;
+    };
+  }, []);
...
   async function startDelete(payload: TPayload) {
+    const generation = ++requestGeneration.current;
     setIsChecking(true);
     try {
       const { canDelete, blockers } = await check(payload);
+      if (generation !== requestGeneration.current) return;
       if (canDelete) {
         confirmDialogHandle.openWithPayload(payload);
       } else {
         showBlocked(toDeleteBlockers(blockers));
       }
     } catch (error) {
-      handleDeleteError(error);
+      if (generation === requestGeneration.current) {
+        handleDeleteError(error);
+      }
     } finally {
-      setIsChecking(false);
+      if (generation === requestGeneration.current) {
+        setIsChecking(false);
+      }
     }
   }

Open the source location

Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 63d4fe4 and 56698ff.

Files selected for processing: 5
  • web/sdk/client/components/delete-blocked-dialog/delete-blocked-dialog.tsx
  • web/sdk/client/hooks/useDeleteFlow.ts
  • web/sdk/client/utils/delete-blockers.ts
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx
  • web/sdk/shared/types.ts
Files skipped from review as they are similar to previous changes: 5
  • web/sdk/shared/types.ts
  • web/sdk/client/hooks/useDeleteFlow.ts
  • web/sdk/client/components/delete-blocked-dialog/delete-blocked-dialog.tsx
  • web/sdk/client/views/general/components/delete-organization-dialog.tsx
  • web/sdk/client/utils/delete-blockers.ts

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

The delete organization dialog gets its 480px width from a CSS class.
Apsara AlertDialog.Content has no width prop, so width={480} did nothing.

DeleteBlocker.count is optional. A refused delete carries no count, so
blockersFromError leaves it out instead of setting 0.

RESOURCES_EXIST has its own default line instead of the generic one.

toDeleteBlockers and the useDeleteFlow check take the proton Blocker,
DeleteBlockedPayload is imported from shared/types only, and the dialog
context is named DeleteBlockedDialogContext.
Comment thread web/sdk/client/hooks/useDeleteCheck.ts Resolved
Comment thread web/sdk/client/utils/delete-blockers.ts Resolved
A handle created at module scope keeps its open state after the view
unmounts. A delete check that returned after the user left General, or
a dialog left open on browser Back, showed the dialog again on the next
visit. Base UI fixes this in v1.7.0 (mui/base-ui#4951), which Apsara
does not ship yet.
…iles

- Remove the enabled option from useTokens. Nothing passes it now.
- Remove the payload cast in DeleteBlockedDialogHost. The handle gives the type.
- Run prettier on the delete blocked dialog, useDeleteCheck and the delete organization dialog.
blockersFromError returns an empty list instead of an UNKNOWN blocker.
useDeleteCheck then shows an "Unable to delete" toast with the generic
message, so the blocked dialog opens only when it has something to list.
@coderabbitai coderabbitai Bot mentioned this pull request Nov 4, 2026
1 of 6 tasks
DeleteBlockedDialog now renders AlertDialog.Content, so a slot replaces
the whole visible dialog: it returns DeleteBlockedDialog, or its own
AlertDialog.Content with any width, layout and buttons. The SDK keeps
the dialog root, which owns the open state.

DeleteBlockedDialogHost is renamed to DeleteBlockedDialogRoot.
Comment thread web/sdk/client/utils/delete-blockers.ts Resolved
Comment thread web/sdk/client/utils/delete-blockers.ts Resolved
A refusal with no PreconditionFailure details means the workspace is disabled. It showed a blocked dialog with one "Disabled workspace" line, and the project dialog called it a resource problem. It now shows a toast for both the workspace and the project delete, and ORGANIZATION_DISABLED is removed.
When the workspace has a positive token balance, the confirmation shows an outline callout below the checkbox: unused complimentary tokens are forfeited, and purchased tokens are refunded within 15–30 days. It replaces the red text under the description and follows the design.
The description and the acknowledgement follow the updated design, and the name input has no placeholder.
Apsara's Callout always renders its actions slot, and the 40px gap before the empty slot made the text wrap early. The empty slot is now hidden, so the text wraps like the design.
@rohanchkrabrty
rohanchkrabrty merged commit 0372984 into main Nov 5, 2026
8 checks passed
@rohanchkrabrty
rohanchkrabrty deleted the feat/workspace-delete-blockers-dialog branch November 5, 2026 08:43

This branch was successfully deployed

1 active deployment
Preview — a1cf4023 Deployed Nov 5, 2026 by vercel[bot]
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.

4 participants