Skip to content

feat(desktop): allow owners to delete hosts - #5566

Merged
Kitenite merged 9 commits into
mainfrom
delete-hosts-settings
Jul 10, 2026
Merged

Kitenite merged 9 commits into
mainfrom
delete-hosts-settings

Conversation

@Kitenite

@Kitenite Kitenite commented Jul 10, 2026 •

Copy link
Copy Markdown
Member

Summary

  • add an owner-only delete action to host settings with typed confirmation and local-host protection
  • verify active organization membership and host ownership inside the delete transaction
  • issue only the host-row delete; do not query, delete, or update workspaces or automations
  • keep the existing workspace foreign key and database schema unchanged
  • optimistically remove the host, restore it on failure, and expose the action through settings search

User impact

Owners can remove hosts directly from Settings. Workspace, file, conversation, and automation data is never mutated by this flow. Existing database constraints remain authoritative.

Validation

  • focused host deletion tests — 6 passed
  • bun run typecheck in packages/db, packages/trpc, and apps/desktop
  • bunx drizzle-kit check
  • bun run lint
  • git diff --check

Open in Stage

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Review 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
📝 Walkthrough

Walkthrough

Host deletion is added across the v2 host API, automation creation checks, desktop optimistic collections, and host settings. Owners can confirm deletion of remote hosts, related records are cleaned up transactionally, and settings search exposes the deletion action.

Changes

Host deletion

Layer / File(s) Summary
Transactional host deletion
packages/trpc/src/router/v2-host/v2-host.ts, packages/trpc/src/router/v2-host/v2-host.test.ts
The router validates membership and ownership, disables related automations, deletes workspaces and the host transactionally, and tests authorization, scoping, and success behavior.
Locked automation access recheck
packages/trpc/src/router/automation/automation.ts, packages/trpc/src/router/automation/automation.test.ts
Automation creation rechecks target-host access with key-share locks before insertion and tests missing-host and missing-membership rejection paths.
Optimistic collection synchronization
apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/..., apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
Adds v2Hosts.deleteHost and forwards persisted Electric deletions to the v2 host API with transaction matching.
Owner deletion controls
apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/*
Adds an owner-only confirmation flow that blocks local hosts, navigates after starting deletion, and reports successful persistence.
Host deletion search entry
apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/*
Registers a shared “Delete host” setting item and tests search results for the new identifier.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Owner
  participant HostSettings
  participant OptimisticActions
  participant ElectricCollection
  participant V2HostAPI
  participant Database
  Owner->>HostSettings: Confirm remote host deletion
  HostSettings->>OptimisticActions: deleteHost(hostId)
  OptimisticActions->>ElectricCollection: Delete v2 host optimistically
  ElectricCollection->>V2HostAPI: Delete using machineId
  V2HostAPI->>Database: Validate ownership and remove related records
  Database-->>V2HostAPI: Return transaction ID
  V2HostAPI-->>ElectricCollection: Return deletion result
  ElectricCollection-->>HostSettings: Resolve persistence
  HostSettings-->>Owner: Show success toast
Loading

Possibly related PRs

  • superset-sh/superset#4468: Extends the same v2 host optimistic collection and Electric synchronization paths for host renaming.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
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.
Title check ✅ Passed The title is concise and accurately states the main change: owner-only host deletion in desktop settings.
Description check ✅ Passed The PR description covers the change summary, user impact, and validation, though some template sections are omitted.
✨ 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 delete-hosts-settings

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.

@ghost

ghost commented Jul 10, 2026 •

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Implement host deletion API with ownership verification
2 Wire host deletion to optimistic collections
3 Create host deletion UI component
4 Integrate delete action into host settings
5 Expose host deletion in settings search
Open in Stage

Chapters generated by Stage for commit e6cb628 on Jul 10, 2026 3:57am UTC.

@Kitenite
Kitenite marked this pull request as ready for review July 10, 2026 01:05
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@github-actions

github-actions Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an owner-only "Delete host" action to the desktop Host Settings page, backed by a new transactional v2Host.delete tRPC mutation that verifies org membership and host ownership with row-level locks, removes synced workspace rows, pauses related automations, and cascades through host memberships. The optimistic UI removes the host from the Electric collection immediately, navigates the user back to the host list, and shows a success toast once the server confirms.

  • Backend (v2-host.ts): Membership is pre-checked outside the transaction for an early exit, then host existence and owner role are re-verified inside with FOR UPDATE locks — a solid TOCTOU-safe pattern. Workspaces are deleted before the host to respect the restrictive FK, and automations are paused with a conditional OR/inArray guard that handles the zero-workspace case correctly.
  • Frontend (DeleteHostSection.tsx): Confirmation dialog with local-host guard; optimistic delete fires before navigation so the host disappears from the list without a loading pause.
  • Tests (v2-host.test.ts): Auth/authz edge cases are well covered, but the code path where a host has no associated workspaces is not exercised.

Confidence Score: 4/5

Safe to merge — the mutation is well-guarded with transactional row locks and multi-layer ownership checks, and the optimistic UI degrades cleanly on failure.

The security model is solid: org membership is pre-checked outside the transaction and host ownership is re-verified inside with FOR UPDATE locks, preventing TOCTOU races. The main gap is test coverage for the zero-workspace deletion code path, where a different WHERE clause is generated but never exercised by the test suite.

packages/trpc/src/router/v2-host/v2-host.test.ts — the zero-workspace happy-path scenario is missing and should be added to cover the eq(automations.targetHostId) only branch.

Important Files Changed

Filename Overview
packages/trpc/src/router/v2-host/v2-host.ts Adds transactional delete mutation with org-membership pre-check, row-level locks for host and ownership, workspace removal, and automation pausing; security model is sound.
packages/trpc/src/router/v2-host/v2-host.test.ts Good coverage of auth/authz edge cases, but the zero-workspace deletion code path is not tested; a bug in that branch would pass undetected.
apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/DeleteHostSection/DeleteHostSection.tsx New danger-zone component with confirmation dialog and local-host guard; setIsDeleting(false) in finally runs after navigation unmounts the component but is harmless in React 18.
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts Adds onDelete handler for the v2Hosts Electric collection; consistent with existing pattern but accesses transaction.mutations[0] without a guard against an empty array.
apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts Adds deleteHost action following the same runHostsMutation pattern as renameHost; straightforward and correct.
apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx Conditionally renders DeleteHostSection only for owners with correct isLocalHost check; clean integration.
apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts Adds HOST_DELETE setting entry with appropriate keywords and shared variant.
apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts Adds a focused test confirming delete host search returns the new setting ID.
apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/DeleteHostSection/index.ts Barrel export only; no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant UI as DeleteHostSection
    participant OCA as useOptimisticCollectionActions
    participant EC as Electric Collection (v2Hosts)
    participant API as tRPC v2Host.delete
    participant DB as Postgres (dbWs)

    U->>UI: Click Delete host and confirm
    UI->>OCA: deleteHost(hostId)
    OCA->>EC: collection.delete(hostId) optimistic
    EC-->>OCA: transaction isPersisted.promise
    OCA-->>UI: transaction
    UI->>UI: setIsOpen(false) navigate to /settings/hosts
    Note over UI: Component unmounts
    EC->>API: onDelete calls v2Host.delete.mutate
    API->>DB: requireActiveOrgMembership pre-check
    DB-->>API: organizationId
    API->>DB: BEGIN TRANSACTION
    DB->>DB: SELECT v2Hosts FOR UPDATE
    DB->>DB: SELECT v2UsersHosts FOR UPDATE role check
    DB->>DB: SELECT v2Workspaces FOR UPDATE
    DB->>DB: UPDATE automations SET enabled false
    DB->>DB: DELETE v2Workspaces
    DB->>DB: DELETE v2Hosts cascade to v2UsersHosts
    DB->>DB: SELECT pg_current_xact_id
    DB-->>API: txid
    API-->>EC: success true txid
    EC-->>UI: isPersisted resolves toast.success
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant UI as DeleteHostSection
    participant OCA as useOptimisticCollectionActions
    participant EC as Electric Collection (v2Hosts)
    participant API as tRPC v2Host.delete
    participant DB as Postgres (dbWs)

    U->>UI: Click Delete host and confirm
    UI->>OCA: deleteHost(hostId)
    OCA->>EC: collection.delete(hostId) optimistic
    EC-->>OCA: transaction isPersisted.promise
    OCA-->>UI: transaction
    UI->>UI: setIsOpen(false) navigate to /settings/hosts
    Note over UI: Component unmounts
    EC->>API: onDelete calls v2Host.delete.mutate
    API->>DB: requireActiveOrgMembership pre-check
    DB-->>API: organizationId
    API->>DB: BEGIN TRANSACTION
    DB->>DB: SELECT v2Hosts FOR UPDATE
    DB->>DB: SELECT v2UsersHosts FOR UPDATE role check
    DB->>DB: SELECT v2Workspaces FOR UPDATE
    DB->>DB: UPDATE automations SET enabled false
    DB->>DB: DELETE v2Workspaces
    DB->>DB: DELETE v2Hosts cascade to v2UsersHosts
    DB->>DB: SELECT pg_current_xact_id
    DB-->>API: txid
    API-->>EC: success true txid
    EC-->>UI: isPersisted resolves toast.success
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/trpc/src/router/v2-host/v2-host.test.ts:240-291
**Zero-workspace path is untested**

The single test that exercises the happy path sets up one workspace (`selectResults.push([{ id: WORKSPACE_ID }])`), so the `workspaceIds.length > 0` branch — which produces a different WHERE clause (`eq(automations.targetHostId, ...)` with no `or`/`inArray`) — is never exercised. A host with no synced workspaces skips the `or` entirely; a bug in that branch (e.g. a typo in the column name) would pass the current test suite undetected.

### Issue 2 of 3
apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/DeleteHostSection/DeleteHostSection.tsx:44-55
**`setIsDeleting(false)` called on an unmounted component**

After `await navigate(...)` resolves, the `DeleteHostSection` component is unmounted (the route is `/settings/hosts/$hostId` and we've navigated to `/settings/hosts`). The `finally` block then calls `setIsDeleting(false)` on the now-unmounted component. React 18 silently ignores such updates, but the state reset is a no-op and the state itself is dead — consider omitting it, or using a ref guard to make the intent explicit.

### Issue 3 of 3
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts:381-387
**Non-defensive access to `transaction.mutations[0]`**

`transaction.mutations[0]` is accessed without guarding against an empty `mutations` array; if it is ever empty the handler would throw a `TypeError` rather than a meaningful error. The existing `onUpdate` handler uses the same pattern, so this is consistent with the codebase convention, but a defensive check would fail loudly with a descriptive message rather than a cryptic undefined-access stack trace.

Reviews (1): Last reviewed commit: "feat(desktop): allow owners to delete ho..." | Re-trigger Greptile

Comment on lines +240 to +291
});

expect(transactionMock).toHaveBeenCalledTimes(1);
expect(selectForMock).toHaveBeenNthCalledWith(1, "update");
expect(selectForMock).toHaveBeenNthCalledWith(2, "update");
expect(selectForMock).toHaveBeenNthCalledWith(3, "update");
expect(selectFromMock).toHaveBeenNthCalledWith(
3,
realDbSchema.v2Workspaces,
);
expect(deleteMock).toHaveBeenNthCalledWith(1, realDbSchema.v2Workspaces);
expect(updateMock).toHaveBeenCalledWith(realDbSchema.automations);
expect(updateSetMock).toHaveBeenCalledWith({ enabled: false });
expect(updateSetMock.mock.calls[0]?.[0]).not.toHaveProperty("targetHostId");
expect(updateSetMock.mock.calls[0]?.[0]).not.toHaveProperty(
"v2WorkspaceId",
);
expect(deleteMock).toHaveBeenNthCalledWith(2, realDbSchema.v2Hosts);
expect(deleteReturningMock).toHaveBeenCalledWith({
machineId: realDbSchema.v2Hosts.machineId,
});
expect(executeMock).toHaveBeenCalledTimes(1);

expect(deleteWhereMock.mock.calls[0]?.[0]).toMatchObject({
conditions: [
{ right: ORGANIZATION_ID, type: "eq" },
{ right: HOST_ID, type: "eq" },
],
type: "and",
});
expect(updateWhereMock.mock.calls[0]?.[0]).toMatchObject({
conditions: [
{ right: ORGANIZATION_ID, type: "eq" },
{
conditions: [
{ right: HOST_ID, type: "eq" },
{ right: [WORKSPACE_ID], type: "inArray" },
],
type: "or",
},
],
type: "and",
});
expect(deleteWhereMock.mock.calls[1]?.[0]).toMatchObject({
conditions: [
{ right: ORGANIZATION_ID, type: "eq" },
{ right: HOST_ID, type: "eq" },
],
type: "and",
});
});
});

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.

P2 Zero-workspace path is untested

The single test that exercises the happy path sets up one workspace (selectResults.push([{ id: WORKSPACE_ID }])), so the workspaceIds.length > 0 branch — which produces a different WHERE clause (eq(automations.targetHostId, ...) with no or/inArray) — is never exercised. A host with no synced workspaces skips the or entirely; a bug in that branch (e.g. a typo in the column name) would pass the current test suite undetected.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/trpc/src/router/v2-host/v2-host.test.ts
Line: 240-291

Comment:
**Zero-workspace path is untested**

The single test that exercises the happy path sets up one workspace (`selectResults.push([{ id: WORKSPACE_ID }])`), so the `workspaceIds.length > 0` branch — which produces a different WHERE clause (`eq(automations.targetHostId, ...)` with no `or`/`inArray`) — is never exercised. A host with no synced workspaces skips the `or` entirely; a bug in that branch (e.g. a typo in the column name) would pass the current test suite undetected.

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

Comment on lines +44 to +55
setIsOpen(false);
await navigate({ to: "/settings/hosts", replace: true });

try {
await transaction.isPersisted.promise;
toast.success(`Deleted "${hostName}"`);
} catch {
// The shared mutation runner reports the error, and the collection
// restores the host without disrupting wherever the user navigated.
} finally {
setIsDeleting(false);
}

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.

P2 setIsDeleting(false) called on an unmounted component

After await navigate(...) resolves, the DeleteHostSection component is unmounted (the route is /settings/hosts/$hostId and we've navigated to /settings/hosts). The finally block then calls setIsDeleting(false) on the now-unmounted component. React 18 silently ignores such updates, but the state reset is a no-op and the state itself is dead — consider omitting it, or using a ref guard to make the intent explicit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/DeleteHostSection/DeleteHostSection.tsx
Line: 44-55

Comment:
**`setIsDeleting(false)` called on an unmounted component**

After `await navigate(...)` resolves, the `DeleteHostSection` component is unmounted (the route is `/settings/hosts/$hostId` and we've navigated to `/settings/hosts`). The `finally` block then calls `setIsDeleting(false)` on the now-unmounted component. React 18 silently ignores such updates, but the state reset is a no-op and the state itself is dead — consider omitting it, or using a ref guard to make the intent explicit.

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +381 to +387
onDelete: async ({ transaction }) => {
const { original } = transaction.mutations[0];
const result = await apiClient.v2Host.delete.mutate({
hostId: original.machineId,
});
return electricTxidMatch(result.txid);
},

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.

P2 Non-defensive access to transaction.mutations[0]

transaction.mutations[0] is accessed without guarding against an empty mutations array; if it is ever empty the handler would throw a TypeError rather than a meaningful error. The existing onUpdate handler uses the same pattern, so this is consistent with the codebase convention, but a defensive check would fail loudly with a descriptive message rather than a cryptic undefined-access stack trace.

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: 381-387

Comment:
**Non-defensive access to `transaction.mutations[0]`**

`transaction.mutations[0]` is accessed without guarding against an empty `mutations` array; if it is ever empty the handler would throw a `TypeError` rather than a meaningful error. The existing `onUpdate` handler uses the same pattern, so this is consistent with the codebase convention, but a defensive check would fail loudly with a descriptive message rather than a cryptic undefined-access stack trace.

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

@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.

🧹 Nitpick comments (1)
packages/trpc/src/router/v2-host/v2-host.test.ts (1)

228-290: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for the no-workspace deletion path.

The current test only covers the case where the host has workspaces (workspaceIds.length > 0). The workspaceIds.length === 0 branch uses a different WHERE clause (eq(automations.targetHostId, input.hostId) without the or/inArray), which is untested.

💡 Suggested additional test
it("pauses only direct-host automations when the host has no workspaces", async () => {
	selectResults.push(
		[{ machineId: HOST_ID }],
		[{ role: "owner" }],
		[], // no workspaces
	);
	hostDeleteResults = [{ machineId: HOST_ID }];
	const caller = createCaller(createContext());

	await expect(caller.v2Host.delete({ hostId: HOST_ID })).resolves.toEqual({
		success: true,
		txid: 456,
	});

	expect(updateWhereMock.mock.calls[0]?.[0]).toMatchObject({
		conditions: [
			{ right: ORGANIZATION_ID, type: "eq" },
			{ right: HOST_ID, type: "eq" },
		],
		type: "and",
	});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/trpc/src/router/v2-host/v2-host.test.ts` around lines 228 - 290, Add
a test alongside the existing host deletion tests covering the no-workspace
branch: configure selectResults with an empty workspace result, invoke
v2Host.delete, and assert success. Verify updateWhereMock uses an and condition
containing ORGANIZATION_ID and HOST_ID directly, without the workspace
inArray/or clause.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/trpc/src/router/v2-host/v2-host.test.ts`:
- Around line 228-290: Add a test alongside the existing host deletion tests
covering the no-workspace branch: configure selectResults with an empty
workspace result, invoke v2Host.delete, and assert success. Verify
updateWhereMock uses an and condition containing ORGANIZATION_ID and HOST_ID
directly, without the workspace inArray/or clause.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f514a0c-693d-40df-87fd-59f892c2f317

📥 Commits

Reviewing files that changed from the base of the PR and between 93c0a40 and 29a999a.

📒 Files selected for processing (9)
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/DeleteHostSection/DeleteHostSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/components/DeleteHostSection/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • packages/trpc/src/router/v2-host/v2-host.test.ts
  • packages/trpc/src/router/v2-host/v2-host.ts

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 9 files

Architecture diagram
sequenceDiagram
    participant User
    participant UI as Desktop UI
    participant Search as Settings Search
    participant Collection as Electric Collection
    participant TRPC as tRPC Server
    participant DB as PostgreSQL (Transactional)

    Note over User,DB: Host Deletion Flow

    User->>UI: Click "Delete host" in Danger Zone
    activate UI

    UI->>UI: Verify not local host
    alt Owner with non-local host
        UI->>UI: Show confirmation dialog
        User->>UI: Confirm deletion
        UI->>Collection: delete(hostId) [optimistic remove]
        deactivate UI

        Collection->>TRPC: onDelete mutation → v2Host.delete({ hostId })
        activate TRPC

        TRPC->>TRPC: requireActiveOrgMembership(ctx)
        TRPC->>TRPC: Verify session and org membership

        Note over TRPC,DB: Transactional block

        TRPC->>DB: BEGIN TRANSACTION
        activate DB

        DB->>DB: SELECT FROM v2_hosts WHERE orgId=id AND hostId=id FOR UPDATE
        DB-->>TRPC: host record or empty

        alt Host not found
            TRPC-->>Collection: NOT_FOUND error
            Collection->>UI: Restore host in collection
        else Host found
            DB->>DB: SELECT FROM v2_users_hosts WHERE orgId, userId, hostId FOR UPDATE
            DB-->>TRPC: role record or empty

            alt Not owner
                TRPC-->>Collection: FORBIDDEN error
                Collection->>UI: Restore host in collection
            else Is owner

                DB->>DB: SELECT FROM v2_workspaces WHERE orgId AND hostId FOR UPDATE
                DB-->>TRPC: workspace IDs

                alt Has workspace IDs
                    DB->>DB: UPDATE automations SET enabled=false WHERE orgId AND (targetHostId=hostId OR v2WorkspaceId IN workspaceIds)
                else No workspace IDs
                    DB->>DB: UPDATE automations SET enabled=false WHERE orgId AND targetHostId=hostId
                end

                DB->>DB: DELETE FROM v2_workspaces WHERE orgId AND hostId
                DB->>DB: DELETE FROM v2_hosts WHERE orgId AND machineId
                DB-->>TRPC: deleted host or empty

                alt Nothing deleted
                    TRPC-->>Collection: NOT_FOUND error
                    Collection->>UI: Restore host in collection
                else Deleted successfully
                    DB->>DB: SELECT txid()
                    DB-->>TRPC: txid
                    DB-->>TRPC: COMMIT TRANSACTION
                    deactivate DB

                    TRPC-->>Collection: { success: true, txid }
                    deactivate TRPC

                    Note over Collection,UI: Persistence confirmed
                    Collection-->>UI: transaction.isPersisted resolved
                    activate UI
                    UI->>UI: Navigate to /settings/hosts
                    UI->>UI: Show toast "Deleted host name"
                    deactivate UI
                end
            end
        end
    else Local host or non-owner
        UI->>UI: Button disabled with tooltip
    end

    Note over User,Search: Settings Search

    User->>Search: Type "delete host"
    Search->>Search: Match SETTING_ITEM_ID.HOST_DELETE
    Search-->>User: Show "Delete host" result
    User->>UI: Navigate to host settings via search result
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@Kitenite Kitenite changed the title feat(desktop): allow owners to delete hosts feat(desktop): let owners delete hosts without workspaces Jul 10, 2026

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 8 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@Kitenite Kitenite changed the title feat(desktop): let owners delete hosts without workspaces feat(desktop): allow owners to delete hosts Jul 10, 2026

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/trpc/src/router/v2-host/v2-host.ts">

<violation number="1">
P2: The `delete` mutation no longer performs an explicit workspace guard before issuing the `DELETE`, so the previous clean `TRPCError({ code: "CONFLICT" })` response for hosts that still have workspaces is gone. Because the DB foreign key will block the deletion at the database level, data integrity is preserved, but the endpoint is now likely to surface a raw database error/500 instead of the intended typed conflict. Consider restoring the explicit `v2Workspaces` existence check (or adding a mapped catch for the FK violation) so callers continue to receive a predictable `CONFLICT` error.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@@ -123,6 +123,93 @@ export const v2HostRouter = {
return { success: true, txid };

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.

P2: The delete mutation no longer performs an explicit workspace guard before issuing the DELETE, so the previous clean TRPCError({ code: "CONFLICT" }) response for hosts that still have workspaces is gone. Because the DB foreign key will block the deletion at the database level, data integrity is preserved, but the endpoint is now likely to surface a raw database error/500 instead of the intended typed conflict. Consider restoring the explicit v2Workspaces existence check (or adding a mapped catch for the FK violation) so callers continue to receive a predictable CONFLICT error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/trpc/src/router/v2-host/v2-host.ts, line 198:

<comment>The `delete` mutation no longer performs an explicit workspace guard before issuing the `DELETE`, so the previous clean `TRPCError({ code: "CONFLICT" })` response for hosts that still have workspaces is gone. Because the DB foreign key will block the deletion at the database level, data integrity is preserved, but the endpoint is now likely to surface a raw database error/500 instead of the intended typed conflict. Consider restoring the explicit `v2Workspaces` existence check (or adding a mapped catch for the FK violation) so callers continue to receive a predictable `CONFLICT` error.</comment>

<file context>
@@ -1,11 +1,6 @@
 import { db, dbWs } from "@superset/db/client";
 import { v2UsersHostRoleValues } from "@superset/db/enums";
-import {
-	members,
-	v2Hosts,
-	v2UsersHosts,
-	v2Workspaces,
-} from "@superset/db/schema";
+import { members, v2Hosts, v2UsersHosts } from "@superset/db/schema";
</file context>

@Kitenite
Kitenite merged commit 56c1668 into main Jul 10, 2026
18 of 19 checks passed
@Kitenite
Kitenite deleted the delete-hosts-settings branch July 10, 2026 17:31

This branch was previously deployed

1 inactive deployment
preview — e6cb6285 Deployed Jul 10, 2026 by Kitenite via Deploy Docs #6350
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